一步一步構建本身的管理系統① 之 Spring Security OAuth + jwt

如今Spring boot 極大的簡化了 Spring Framework 應用程序的配置,經過簡單的依賴配置便可實現程序的基本安全。html

首先咱們先了解一下 oauth 2.0java

OAuth 2.0 的規範能夠參考 : RFC 6749git

OAuth 是一個開放標準,容許用戶讓第三方應用訪問該用戶在某一網站上存儲的私密的資源(如照片,視頻,聯繫人列表),而無需將用戶名和密碼提供給第三方應用。目前,OAuth 的最新版本爲 2.0web

OAuth 容許用戶提供一個令牌,而不是用戶名和密碼來訪問他們存放在特定服務提供者的數據。每個令牌受權一個特定的網站(例如,視頻編輯網站)在特定的時段(例如,接下來的2小時內)內訪問特定的資源(例如僅僅是某一相冊中的視頻)。這樣,OAuth 容許用戶受權第三方網站訪問他們存儲在另外的服務提供者上的信息,而不須要分享他們的訪問許可或他們數據的全部內容。spring

 

  • resource owner:資源全部者,指終端的「用戶」(user)
  • resource server:資源服務器,即服務提供商存放受保護資源。訪問這些資源,須要得到訪問令牌(access token)。它與認證服務器,能夠是同一臺服務器,也能夠是不一樣的服務器。若是,咱們訪問新浪博客網站,那麼若是使用新浪博客的帳號來登陸新浪博客網站,那麼新浪博客的資源和新浪博客的認證都是同一家,能夠認爲是同一個服務器。若是,咱們是新浪博客帳號去登陸了知乎,那麼顯然知乎的資源和新浪的認證不是一個服務器。
  • client:客戶端,表明向受保護資源進行資源請求的第三方應用程序。
  • authorization server: 受權服務器, 在驗證資源全部者並得到受權成功後,將發放訪問令牌給客戶端。 
          

在咱們的系統中咱們須要 添加依賴庫。sql

· spring-boot-starter-web數據庫

· spring-boot-starter-security安全

· spring-boot-starter-web服務器

· spring-security-jwt session

· spring-security-oauth2

請看這裏

配置的主要目錄結構是這樣的

首先配置Spring boot 的配置文件 application.properties

security.oauth2.resource.filter-order=3

security.signing-key=MaYzkSjmkzPC57L
security.security-name=fmanager

security.jwt.client-id=testjwtclientid
security.jwt.client-secret=XY7kmzoNzl100
security.jwt.client-role=ADMIN_USER
security.jwt.grant-type=password
security.jwt.scope-read=read
security.jwt.scope-write=write
security.jwt.resource-ids=testjwtresourceid

#須要的是上面部分

# LOGGING
logging.level.org.springframework.web=DEBUG
logging.level.main.java.com.fmanager = DEBUG


spring.datasource.url=jdbc:postgresql://localhost:5432/fmanager
spring.datasource.username=xxxxx
spring.datasource.password=xxxx

mybatis.config-locations=classpath:mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

安全配置,

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${security.signing-key}")
    private String signingKey;
    
    @Value("${security.security-name}")
    private String securityRealm;
    
    
    @Resource
    private UserDetailsService userDetailsService; 
    
    
    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
       return super.authenticationManager();
    }
    
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       auth.userDetailsService(userDetailsService)
               .passwordEncoder(new BCryptPasswordEncoder());
    }
    
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
       http
               .sessionManagement()
               .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
               .and()
               .httpBasic()
               .realmName(securityRealm)
               .and()
               .csrf()
               .disable().authorizeRequests().antMatchers("/oauth/token").permitAll(); //$NON-NLS-1$

    }
    
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(signingKey);
        return converter;
    }
    
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }
    
    @Bean
    @Primary //Making this primary to avoid any accidental duplication with another token service instance of the same name
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }  
}
  • 注意 註解要添加
  • JwtTokenStore 和 JwtAccessTokenConverter beans: JwtAccessTokenConverter 在受權服務器和資源服務器中要使用,JwtTokenStore 用於在資源服務器中decode token.
  • UserDetailsService:  咱們注入了一個本身的UserDetailsServiceImpl (點擊查看)service,  這樣能夠實現經過用戶名密碼認證
  • 密碼加密就用spring 提供的 BCryptPasswordEncoder

受權服務器配置

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

	@Value("${security.jwt.client-id}")
	private String clientId;

	@Value("${security.jwt.client-secret}")
	private String clientSecret;

	@Value("${security.jwt.grant-type}")
	private String grantType;

	@Value("${security.jwt.scope-read}")
	private String scopeRead;

	@Value("${security.jwt.scope-write}")
	private String scopeWrite = "write"; //$NON-NLS-1$

	@Value("${security.jwt.resource-ids}")
	private String resourceIds;

	@Autowired
	private TokenStore tokenStore;

	@Autowired
	private JwtAccessTokenConverter accessTokenConverter;

	@Autowired
	private AuthenticationManager authenticationManager;

	@Autowired
	private PasswordEncoder passwordEncoder;

	@Override
	public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {

		configurer.inMemory().withClient(clientId).secret(passwordEncoder.encode(clientSecret))
				.authorizedGrantTypes(grantType).scopes(scopeRead, scopeWrite).resourceIds(resourceIds);
	}

	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
		TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
		enhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
		endpoints.tokenStore(tokenStore).accessTokenConverter(accessTokenConverter).tokenEnhancer(enhancerChain)
				.authenticationManager(authenticationManager);
	}

	@Bean
	private static DelegatingPasswordEncoder passwordEncoder() {
		String idForEncode = "bcrypt"; //$NON-NLS-1$
		Map<String, PasswordEncoder> encoderMap = new HashMap<>();
		encoderMap.put(idForEncode, new BCryptPasswordEncoder());
		return new DelegatingPasswordEncoder(idForEncode, encoderMap);
	}

}
  • 繼承AuthorizationServerConfigurerAdapter , 實現裏面的配置
  • Client id,Client secret, 提供認證的客戶端id 和密碼
  • scope : 定義訪問資源等級
  • AuthenticationManager : Spring 的驗證管理器

資源服務器配置

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
    @Autowired
    private ResourceServerTokenServices tokenServices;
    
    
    @Value("${security.jwt.resource-ids}")
    private String resourceIds;
    
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId(resourceIds).tokenServices(tokenServices);
    }
    
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
                http
                .requestMatchers()
                .and()
                .authorizeRequests()
                .antMatchers("/**").authenticated(); //$NON-NLS-1$
    }

}
  • configure 方法裏面配置資源訪問, 這裏我配置了全部url 都須要認證

配置基本完成, 鏈接數據庫的配置也很簡單。  在application.properties中添加mybastis 配置以及spring 鏈接配置

spring.datasource.url=jdbc:postgresql://localhost:5432/fmanager
spring.datasource.username=xxxxx
spring.datasource.password=xxxx

mybatis.config-locations=classpath:mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

寫對應mapper 就能夠了

好比:

@Mapper
public interface UserDAO {
	public User findById(long id);
	
	public User findByUserName(String name);
}
  • 注意註解(Mapper)要加上, 不讓系統不會自動加載的

好,先寫一個測試Controller

@RestController
@RequestMapping("/test")
public class TestController {
	
    @Autowired
    private UserServcie userService;
    
	@RequestMapping(value="/greeting",method = RequestMethod.GET)
    public String greeting() {
	    System.out.println(userService.findById(1));
        return "Hello";
    }
	
	
}

用rest client 訪問就會看到

沒有權限。

咱們這裏使用用戶名密碼獲取token

獲取這個token 是記得要加

Content-Type:application/x-www-form-urlencoded
authorization: Basic dGVzdGp3dGNsaWVudGlkOlhZN2ttem9OemwxMDA=

這個authorization 是經過 以前的client_id 和client_secret base64 獲得的。

獲得這個token, 

而後咱們再訪問測試controller 的時候帶上這個token 就能夠正常訪問了

相關文章
相關標籤/搜索