SpringSecurityOAuth2之JWT应用示例

  本文记录一下使用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)

 

posted @ 2020-06-13 23:24  雷雨客  阅读(2301)  评论(1编辑  收藏  举报