美文网首页SpringBoot
SpringSecurity OAuth2 自定义令牌配置(JW

SpringSecurity OAuth2 自定义令牌配置(JW

作者: lconcise | 来源:发表于2019-09-28 10:32 被阅读0次

    目录:

    • 自定义令牌配置
    • 使用JWT替换默认令牌
    • 扩展JWT
    • JAVA中解析JWT
    • 刷新令牌

    Spring Security允许我们自定义令牌配置,比如不同的client_id对应不同的令牌,令牌的有效时间,令牌的存储策略等;我们也可以使用JWT来替换默认的令牌。

    自定义令牌配置

    我们让认证服务器AuthorizationServerConfig继承AuthorizationServerConfigurerAdapter,并重写它的configure(ClientDetailsServiceConfigurer clients)方法:

    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private AuthenticationManager authenticationManager;
        @Autowired
        private TokenEnhancer tokenEnhancer;
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            endpoints.authenticationManager(authenticationManager)
                    .userDetailsService(userDetailService);
        }
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                    .withClient("test1")
                    .secret(new BCryptPasswordEncoder().encode("test1111"))
                    .accessTokenValiditySeconds(3600)
                    .refreshTokenValiditySeconds(864000)
                    .scopes("all", "a", "b", "c")
                    .authorizedGrantTypes("password")
                    .and()
                    .withClient("test2")
                    .secret(new BCryptPasswordEncoder().encode("test2222"))
                    .accessTokenValiditySeconds(7200);
        }
    }
    

    认证服务器在继承了AuthorizationServerConfigurerAdapter适配器后,需要重写configure(AuthorizationServerEndpointsConfigurer endpoints)方法,指定 AuthenticationManager和UserDetailService。

    创建一个新的配置类SecurityConfig,在里面注册我们需要的AuthenticationManagerBean:

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    }
    

    此外,重写configure(ClientDetailsServiceConfigurer clients)方法主要配置了:

    1. 定义两个client_id,及客户端可以通过不同的client_id来获取不同的令牌;
    2. client_id为test1的令牌有效时间为3600秒,client_id为test2的令牌有效时间为7200秒;
    3. client_id为test1的refresh_token(下面会介绍到)有效时间为864000秒,即10天,也就是说在这10天内都可以通过refresh_token来换取新的令牌;
    4. 在获取client_id为test1的令牌的时候,scope只能指定为all,a,b或c中的某个值,否则将获取失败;
    5. 只能通过密码模式(password)来获取client_id为test1的令牌,而test2则无限制。

    启动项目后使用密码模式获取test1的令牌:


    image.png

    和前面介绍的那样,头部需要传入test1:test1111经过base64加密后的值:


    image.png
    返回结果:
    {
        "access_token": "3f9864cc-dab5-420a-b129-560f409922d6",
        "token_type": "bearer",
        "expires_in": 3599,
        "scope": "all"
    }
    

    使用JWT替换默认令牌

    使用JWT替换默认的令牌(默认令牌使用UUID生成)只需要指定TokenStore为JwtTokenStore即可。

    创建一个JWTokenConfig配置类:

    @Configuration
    public class JWTokenConfig {
    
        @Bean
        public TokenStore jwtTokenStore() {
            return new JwtTokenStore(jwtAccessTokenConverter());
        }
    
        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter() {
            JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
            accessTokenConverter.setSigningKey("test_key"); // 签名密钥
            return accessTokenConverter;
        }
    }
    

    签名密钥为test_key。在配置类里配置好JwtTokenStore后,我们在认证服务器里指定它:

    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private AuthenticationManager authenticationManager;
        @Autowired
        private UserDetailService userDetailService;
        @Autowired
        private TokenStore jwtTokenStore;
        @Autowired
        private JwtAccessTokenConverter jwtAccessTokenConverter;
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
    
            endpoints.authenticationManager(authenticationManager)
                    .tokenStore(jwtTokenStore)
                    .accessTokenConverter(jwtAccessTokenConverter)
                    .userDetailsService(userDetailService);
        }
    
    ...
    }
    

    重启服务获取令牌,系统将返回如下格式令牌:

    {
        "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Njg3ODkzNTIsInVzZXJfbmFtZSI6InVzZXIiLCJhdXRob3JpdGllcyI6WyJhZG1pbiJdLCJqdGkiOiI2NzhlZTc5NC1lYzhhLTQxOTYtYmE2NS0xM2QwNjRiOGEyZWUiLCJjbGllbnRfaWQiOiJ0ZXN0MSIsInNjb3BlIjpbImFsbCJdfQ.ojuPewlJ_3hWRwOrlCwcHFZaNIX_78FQwj86Inw79_w",
        "token_type": "bearer",
        "expires_in": 3599,
        "scope": "all",
        "jti": "678ee794-ec8a-4196-ba65-13d064b8a2ee"
    }
    

    access_token中的内容复制到https://jwt.io/网站解析下:

    image.png

    拓展JWT

    上面的Token解析得到的PAYLOAD内容为:

    {
      "exp": 1568789352,
      "user_name": "user",
      "authorities": [
        "admin"
      ],
      "jti": "678ee794-ec8a-4196-ba65-13d064b8a2ee",
      "client_id": "test1",
      "scope": [
        "all"
      ]
    }
    

    如果想在JWT中添加一些额外的信息,我们需要实现TokenEnhancer(Token增强器):

    public class JWTTokenEnhancer implements TokenEnhancer {
        @Override
        public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
            Map<String, Object> info = new HashMap<>();
            info.put("message", "hello world");
            ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info);
            return oAuth2AccessToken;
        }
    }
    

    我们在Token中添加了message: hello world信息。然后在JWTokenConfig里注册该Bean:

    @Configuration
    public class JWTokenConfig {
        ......
    
        @Bean
        public TokenEnhancer tokenEnhancer() {
            return new JWTokenEnhancer();
        }
    }
    

    最后在认证服务器里配置该增强器:

    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private AuthenticationManager authenticationManager;
        @Autowired
        private UserDetailService userDetailService;
        @Autowired
        private TokenStore jwtTokenStore;
        @Autowired
        private JwtAccessTokenConverter jwtAccessTokenConverter;
        @Autowired
        private TokenEnhancer tokenEnhancer;
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
            List<TokenEnhancer> enhancers = new ArrayList<>();
            enhancers.add(tokenEnhancer);
            enhancers.add(jwtAccessTokenConverter);
            enhancerChain.setTokenEnhancers(enhancers);
    
            endpoints.authenticationManager(authenticationManager)
                    .tokenStore(jwtTokenStore)
                    .tokenEnhancer(enhancerChain)
                    .accessTokenConverter(jwtAccessTokenConverter)
                    .userDetailsService(userDetailService);
        }
    ...
    }
    

    重启项目,再次获取令牌,系统返回:

    {
        "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTU2ODc4OTkzNSwibWVzc2FnZSI6ImhlbGxvIHdvcmxkIiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiZDlmM2M4NGItMWNlNy00YWJmLWE1ZjUtODlkMTVmMTA2YTRhIiwiY2xpZW50X2lkIjoidGVzdDEifQ.WjIvH4dpaF8oKXh1qWuSP5o4slgpG9fAWzGTBUrwlA4",
        "token_type": "bearer",
        "expires_in": 3599,
        "scope": "all",
        "message": "hello world",
        "jti": "d9f3c84b-1ce7-4abf-a5f5-89d15f106a4a"
    }
    

    可以看到,在返回的JSON内容里已经多了我们添加的message信息,此外将access_token复制到jwt.io网站解析,内容如下:

    {
      "user_name": "user",
      "scope": [
        "all"
      ],
      "exp": 1568789935,
      "message": "hello world",
      "authorities": [
        "admin"
      ],
      "jti": "d9f3c84b-1ce7-4abf-a5f5-89d15f106a4a",
      "client_id": "test1"
    }
    

    解析后的JWT也包含了我们添加的message信息。

    Java中解析JWT

    要在Java代码中解析JWT,需要添加如下依赖:

    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
    

    编写接口index

        @GetMapping("/index")
        public Object index(HttpServletRequest request) {
            String header = request.getHeader("Authorization");
            String token = StringUtils.substringAfter(header, "bearer ");
    
            return Jwts.parser().setSigningKey("test_key".getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token).getBody();
        }
    

    signkey需要和JwtAccessTokenConverter中指定的签名密钥一致。重启项目,获取令牌后访问/index,输出内容如下:

    {
        "user_name": "user",
        "scope": [
            "all"
        ],
        "exp": 1568790296,
        "message": "hello world",
        "authorities": [
            "admin"
        ],
        "jti": "4da297b1-9c12-4251-bf18-93bbc35d25bd",
        "client_id": "test1"
    }
    

    刷新令牌

    令牌过期后我们可以使用refresh_token来从系统中换取一个新的可用令牌。但是从前面的例子可以看到,在认证成功后返回的JSON信息里并没有包含refresh_token,要让系统返回refresh_token,需要在认证服务器自定义配置里添加如下配置:

    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        ......
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                    .withClient("test1")
                    .secret(new BCryptPasswordEncoder().encode("test1111"))
                    .authorizedGrantTypes("password", "refresh_token")
                    .accessTokenValiditySeconds(3600)
                    .refreshTokenValiditySeconds(864000)
                    .scopes("all", "a", "b", "c")
                .and()
                    .withClient("test2")
                    .secret(new BCryptPasswordEncoder().encode("test2222"))
                    .accessTokenValiditySeconds(7200);
        }
    }
    

    授权方式需要加上refresh_token,除了四种标准的OAuth2获取令牌方式外,Spring Security OAuth2内部把refresh_token当作一种拓展的获取令牌方式。

    通过上面的配置,使用test1这个client_id获取令牌时将返回refresh_token,refresh_token的有效期为10天,即10天之内都可以用它换取新的可用令牌。

    重启项目,认证成功后,系统返回如:

    {
        "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTU2ODc5MDY4OCwibWVzc2FnZSI6ImhlbGxvIHdvcmxkIiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiMGMwZjg3N2ItMmNkYy00NmI2LWJkYTktNThhYmMzMmNkZDQ3IiwiY2xpZW50X2lkIjoidGVzdDEifQ.RmZExfQmbPgCo2UR8HChaTxRoUzkmKB2r2h7quSAUrw",
        "token_type": "bearer",
        "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6IjBjMGY4NzdiLTJjZGMtNDZiNi1iZGE5LTU4YWJjMzJjZGQ0NyIsImV4cCI6MTU2OTY1MTA4OCwibWVzc2FnZSI6ImhlbGxvIHdvcmxkIiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiNDJmNTQyMjQtNDJiZS00ZWE2LTg5ODktOGE3ZTFhNjkzMjA5IiwiY2xpZW50X2lkIjoidGVzdDEifQ.1MLmQYoh--ExRuuf0_glHApPnTyCBi9UbZoZM3-76Ds",
        "expires_in": 3599,
        "scope": "all",
        "message": "hello world",
        "jti": "0c0f877b-2cdc-46b6-bda9-58abc32cdd47"
    }
    

    假设现在access_token过期了,我们用refresh_token去换取新的令牌。使用postman发送如下请求:


    image.png
    image.png
    {
        "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTU2ODc5MDgxMSwibWVzc2FnZSI6ImhlbGxvIHdvcmxkIiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiMjdkOTNkMWQtNzczNi00ODQzLThjOTQtZWI1ZjdkMzIyMWJlIiwiY2xpZW50X2lkIjoidGVzdDEifQ.733DihtA3G3GkfFT82Bu-Z3FYuWHWxX8l_A0hON3XO8",
        "token_type": "bearer",
        "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6IjI3ZDkzZDFkLTc3MzYtNDg0My04Yzk0LWViNWY3ZDMyMjFiZSIsImV4cCI6MTU2OTY1MTA4OCwibWVzc2FnZSI6ImhlbGxvIHdvcmxkIiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiNDJmNTQyMjQtNDJiZS00ZWE2LTg5ODktOGE3ZTFhNjkzMjA5IiwiY2xpZW50X2lkIjoidGVzdDEifQ.61o51OwW7twZ3tDRzEu__ho0bwwpIgwDPytkpnF00u8",
        "expires_in": 3599,
        "scope": "all",
        "message": "hello world",
        "jti": "27d93d1d-7736-4843-8c94-eb5f7d3221be"
    }
    

    源码地址:https://github.com/lbshold/springboot/tree/master/Spring-Security-OAuth2-JWT

    相关文章

      网友评论

        本文标题:SpringSecurity OAuth2 自定义令牌配置(JW

        本文链接:https://www.haomeiwen.com/subject/utzhuctx.html