本文記錄一下使用SpringSecurityOAuth2配置JWT的步驟
1、相關知識
OAuth協議簡介:https://www.cnblogs.com/javasl/p/13054133.html
OAuth 2.0官網:https://oauth.net/2/
使用SpringSecurityOAuth2默認實現OAuth2授權示例:https://www.cnblogs.com/javasl/p/13060284.html
使用SpringSecurityOAuth2配置自定義Token實現OAuth2授權示例:https://www.cnblogs.com/javasl/p/13068613.html
2、JWT基礎
JWT全稱是Json Web Token,它是JSON的一個開放的Token標准。JWT有三個特點:
1)自包含。JWT中包含着Token有意義的信息,拿到Token,解析后就能知道里面包含的信息是什么,而Spring默認生成的Token是UUID,沒有任何有意義的信息。它的信息需要根據這個Token去Redis中讀取。
2)密簽。發出去的令牌不包含密碼等敏感信息,使用指定的秘鑰簽名。
3)可擴展。包含的信息可以根據業務需求自己定義。
2、構建項目
本文使用的springboot版本是2.0.4.RELEASE,不同版本可能會有所區別。下面是主要的配置文件和類:
1)pom依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.1.3.RELEASE</version> </dependency>
2)application.properties
#不需要,暫時寫死在代碼中,重構時移植到此處即可
3)主配置類
@EnableWebSecurity @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { http.httpBasic().and().csrf().disable(); } @Bean("authenticationManager") public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
4)用戶認證類
@Component public class MyUserDetailsService implements UserDetailsService{ @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { System.out.println("登錄用戶名:"+username); String password = passwordEncoder.encode("123456"); return new User(username,password,true,true,true,true, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_USER")); } }
5)認證服務類
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{ @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Autowired private TokenStore tokenStore; @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter;
@Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .tokenStore(tokenStore)//如果使用RedisTokenStore,則會把Token存入redis中,否則存在內存 .accessTokenConverter(jwtAccessTokenConverter) .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients .inMemory()//Token保存在內存中 .withClient("MyProject").secret(passwordEncoder.encode("MyProject_123"))//指明client-id和client-secret .accessTokenValiditySeconds(7200)//令牌有效時間,單位秒 .authorizedGrantTypes("refresh_token","password","authorization_code")//支持刷新令牌、密碼模式、授權碼模式 .scopes("all","read","write")//權限有哪些,如果這兩配置了該參數,客戶端發請求可以不帶參數,使用配置的參數 .redirectUris("http://127.0.0.1:8080/login"); } }
6)JWT配置類
@Configuration public class JwtTokenConfig { @Bean public TokenStore jwtTokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter(); accessTokenConverter.setSigningKey("shxiang");//設置秘鑰 return accessTokenConverter; } }
說明:上一篇講到RedisTokenStore,這里又有JwtTokenStore,一定要區分開。
a)如果TokenStore的注入類型是RedisTokenStore,則生成的JWT會存入redis中。
b)如果TokenStore的注入類型是JwtTokenStore,或者endpoints不設置TokenStore,那么生成的JWT在內存中。
7)啟動類
@SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
4、測試驗證
1)獲取Token成功
2)將Token復制到https://www.jsonwebtoken.io/解析Token成功
3)根據Token獲取當前用戶信息
@GetMapping("/me") public Object getCurrentUser(Authentication user) { return user; }
說明:上面是密碼模式的測試,授權碼模式的測試請參考上一篇的測試驗證部分。
5、擴展自定義字段
1)添加Token增強類
public class MyJwtTokenEnhancer implements TokenEnhancer{ @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Map<String,Object> info = new HashMap<String, Object>(); info.put("company","test test test");//拓展的字段 ((DefaultOAuth2AccessToken)accessToken).setAdditionalInformation(info); return accessToken; } }
2)修改JwtTokenConfig.java,添加jwtTokenEnhancer方法創建bean
@Bean @ConditionalOnMissingBean(name = "jwtTokenEnhancer") public TokenEnhancer jwtTokenEnhancer() { return new MyJwtTokenEnhancer(); }
3)修改AuthorizationServerConfig.java的configure方法,如下:
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{ ... ... @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; @Autowired private TokenEnhancer jwtTokenEnhancer; @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); TokenEnhancerChain enhancerChain = new TokenEnhancerChain(); List<TokenEnhancer> enhancers = new ArrayList<>(); enhancers.add(jwtTokenEnhancer); enhancers.add(jwtAccessTokenConverter); enhancerChain.setTokenEnhancers(enhancers); endpoints.tokenEnhancer(enhancerChain); endpoints.accessTokenConverter(jwtAccessTokenConverter); } ... ... }
測試結果如下:
注意:拓展的字段不會封裝到用戶信息Authentication中,執行http://localhost:8080/me不會獲取到拓展字段。
6、獲取自定義字段
1)添加pom.xml依賴
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency>
2)測試方法如下
@GetMapping("/me") public Object getCurrentUser(Authentication user,HttpServletRequest request) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException, UnsupportedEncodingException { String header = request.getHeader("Authorization"); String token = StringUtils.substringAfter(header,"bearer "); Claims claims = Jwts.parser().setSigningKey("shxiang".getBytes("UTF-8"))//shxiang是秘鑰 .parseClaimsJws(token).getBody(); String company = (String)claims.get("company"); System.out.println("-------company----------:"+company); return user; }
結果如下:
7、Token的刷新
獲取令牌的時候會獲取access_token和refresh_token,如下圖所示:
使用refresh_token可以刷新Token,如下:
當用戶使用Token訪問時,發現超時了,在無感知的情況下立馬使用refresh_token請求一個新的Token。refresh_token設置較長時間,如:
.accessTokenValiditySeconds(7200)
.refreshTokenValiditySeconds(2592000)