Spring Security OAuth2整合JWT

文章目录

  • 引言
  • JWT的三个特点
    • 自包含:
    • 密签:
      • 签名:
      • 加密:
    • 可扩展:
  • JWT组成
    • Header
    • Claims (Payload)
    • Signature
    • JWT流程示意图
    • Spring Security Oauth2 实现JWT
      • 配置TokenStoreConfig用于存储Token
      • MerryyouJwtTokenEnhancer
      • 配置认证服务器
      • 配置资源服务器
      • 解析扩展的Token
      • 测试方法
  • 刷新令牌:

引言


Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

JWT的三个特点


自包含:

包含有意义的信息,令牌里面是有信息的,拿到这个令牌之后直接解析令牌就可以知道里边的信息是什么,不需要再去存储里边读一个东西

密签:

密签是一种签名而不是加密,JWT是一种开放的标准,发出去的秘钥信息是按照标准方式生成的,所有人是可以看到里边的信息,所以不要把敏感的信息放进去

签名:

防止别人篡改,发出去的令牌,如果里边信息被改变了,你是可以知道的

加密:

里边的信息别人不能被破解

可扩展:

可以自定义放进去的东西
Spring Security OAuth2整合JWT_第1张图片
默认方式生成一个令牌,因为是用UUID的方式,最后得到一个没有意义的串,这种机制的特点是:依赖一个存储,一旦存储出现问题了,这个串就没有任何意义了
Spring Security OAuth2整合JWT_第2张图片

JWT组成


Spring Security OAuth2整合JWT_第3张图片

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE1MTY3MjY4MTMsImJsb2ciOiJodHRwczovL2xvbmdmZWl6aGVuZy5naXRodWIuaW8vIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9VU0VSIl0sImp0aSI6ImJmZmY0NjRjLTFiNTktNGZkNy1hNTE4LWU3YjY5MDFiNzU3YyIsImNsaWVudF9pZCI6Im1lcnJ5eW91In0.gp5t9nY9mGp5O2-yqdflc0nEAsTeCQG7VugA8q8XcF4

Header

Header 包含了一些元数据,至少会表明 token 类型以及 签名方法。

{
     
 "typ": "JWT",
 "alg": "HS256"
}

Claims (Payload)

Claims部分包含了一些跟这个 token有关的重要信息。

{
     
 "user_name": "admin",
 "scope": [
  "all"
 ],
 "exp": 1516726813,
 "blog": "https://longfeizheng.github.io/",
 "authorities": [
  "ROLE_USER"
 ],
 "jti": "bfff464c-1b59-4fd7-a518-e7b6901b757c",
 "client_id": "merryyou"
}

Signature

JWT标准遵照 JSON Web Signature (JWS) 标准来生成签名。签名主要用于验证 token是否有效,是否被篡改。

JWT流程示意图

Spring Security OAuth2整合JWT_第4张图片

Spring Security Oauth2 实现JWT

配置TokenStoreConfig用于存储Token

@Configuration
public class TokenStoreConfig {
     
    /**
     * redis连接工厂
     */
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    /**
     * 用于存放token
     * @return
     */
    @Bean
    @ConditionalOnProperty(prefix = "merryyou.security.oauth2", name = "storeType", havingValue = "redis")
    public TokenStore redisTokenStore() {
     
        return new RedisTokenStore(redisConnectionFactory);
    }

    /**
     * jwt TOKEN配置信息
     */
    @Configuration
    @ConditionalOnProperty(prefix = "merryyou.security.oauth2", name = "storeType", havingValue = "jwt", matchIfMissing = true)
    public static class JwtTokenCofnig{
     

        /**
         * 使用jwtTokenStore存储token
         * @return
         */
        @Bean
        public TokenStore jwtTokenStore(){
     
            return new JwtTokenStore(jwtAccessTokenConverter());
        }

        /**
         * 用于生成jwt
         * @return
         */
        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter(){
     
            JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
            accessTokenConverter.setSigningKey("merryyou");//生成签名的key
            return accessTokenConverter;
        }

        /**
         * 用于扩展JWT
         * @return
         */
        @Bean
        @ConditionalOnMissingBean(name = "jwtTokenEnhancer")
        public TokenEnhancer jwtTokenEnhancer(){
     
            return new MerryyouJwtTokenEnhancer();
        }

    }
}

MerryyouJwtTokenEnhancer

public class MerryyouJwtTokenEnhancer implements TokenEnhancer {
     
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
     
        Map<String, Object> info = new HashMap<>();
        info.put("blog", "https://longfeizheng.github.io/");//扩展返回的token
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
        return accessToken;
    }
}

配置认证服务器

@Configuration
@EnableAuthorizationServer
public class MerryyouAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
     

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private TokenStore tokenStore;

    @Autowired(required = false)
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired(required = false)
    private TokenEnhancer jwtTokenEnhancer;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
     
        endpoints.tokenStore(tokenStore)
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
        //扩展token返回结果
        if (jwtAccessTokenConverter != null && jwtTokenEnhancer != null) {
     
            TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
            List<TokenEnhancer> enhancerList = new ArrayList();
            enhancerList.add(jwtTokenEnhancer);
            enhancerList.add(jwtAccessTokenConverter);
            tokenEnhancerChain.setTokenEnhancers(enhancerList);
            //jwt
            endpoints.tokenEnhancer(tokenEnhancerChain)
                    .accessTokenConverter(jwtAccessTokenConverter);
        }
    }

    /**
     * 配置客户端一些信息
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
     
        clients.inMemory()
                .withClient("merryyou")
                .secret("merryyou")
                .accessTokenValiditySeconds(7200)
                .authorizedGrantTypes("refresh_token", "password", "authorization_code")//OAuth2支持的验证模式
                .scopes("all");
    }
}

配置资源服务器

@Configuration
@EnableResourceServer
public class MerryyouResourceServerConfig extends ResourceServerConfigurerAdapter {
     

    /**
     * 自定义登录成功处理器
     */
    @Autowired
    private AuthenticationSuccessHandler appLoginInSuccessHandler;

    @Override
    public void configure(HttpSecurity http) throws Exception {
     
        http.formLogin()
                .successHandler(appLoginInSuccessHandler)//登录成功处理器
                .and()
                .authorizeRequests().anyRequest().authenticated().and()
                .csrf().disable();
    }

}

解析扩展的Token

可扩展:往里边加东西
将一些自定义的信息加到accessToken里边
可以根据不同业务来覆盖掉默认的Enhancer逻辑

创建accessToken的时候,new的是一个DefaultOAuth2AccessToken(UUID),这个行为写在私有方法里边,而且没有接口的封装,意味着这块代码是不可变的,是无法改变的,只能通过自己的增强器去改变Token里边的内容,把UUID转成jwt然后往里边增加信息,所以要用一个链(chain)把两个增强器,一个是转换成jwt,一个是往里边加信息加内容,把这两个增强器连起来,
Spring Security OAuth2整合JWT_第5张图片
想在业务方法里用jwt增强的信息,Authentication里面没有额外的信息,自己写一些代码去解析jwt,然后拿出想要的数据

@GetMapping("/user")
    public Object getCurrentUser1(Authentication authentication, HttpServletRequest request) throws UnsupportedEncodingException {
     
        log.info("【SecurityOauth2Application】 getCurrentUser1 authenticaiton={}", JsonUtil.toJson(authentication));

        String header = request.getHeader("Authorization");
        String token = StringUtils.substringAfter(header, "bearer ");

        Claims claims = Jwts.parser().setSigningKey("merryyou".getBytes("UTF-8")).parseClaimsJws(token).getBody();
        String blog = (String) claims.get("blog");
        log.info("【SecurityOauth2Application】 getCurrentUser1 blog={}", blog);

        return authentication;
    }

测试方法

 @Test
    public void signInTest() throws Exception {
     
        RestTemplate rest = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.add("authorization", getBasicAuthHeader());

        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("username", "admin");
        params.add("password", "123456");

        HttpEntity<?> entity = new HttpEntity(params, headers);
        // pay attention, if using get with headers, should use exchange instead of getForEntity / getForObject
        ResponseEntity<String> result = rest.exchange(SIGN_IN_URI, HttpMethod.POST, entity, String.class, new Object[]{
     null});
        log.info("登录信息返回的结果={}", JsonUtil.toJson(result));
    }

打印:

 "body": "{\"access_token\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE1MTY3MjkxNDIsImJsb2ciOiJodHRwczovL2xvbmdmZWl6aGVuZy5naXRodWIuaW8vIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9VU0VSIl0sImp0aSI6IjMzOTUxNDk1LTBjOGYtNGQ5NS1iZDYyLTAxMjEyYWNjZDU1ZCIsImNsaWVudF9pZCI6Im1lcnJ5eW91In0.7Lrpmn3CaNweqcMeADJeZJGDTEZYN-gg5OpAzbKIEqQ\",\"token_type\":\"bearer\",\"refresh_token\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiIzMzk1MTQ5NS0wYzhmLTRkOTUtYmQ2Mi0wMTIxMmFjY2Q1NWQiLCJleHAiOjE1MTkzMTM5NDIsImJsb2ciOiJodHRwczovL2xvbmdmZWl6aGVuZy5naXRodWIuaW8vIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9VU0VSIl0sImp0aSI6IjFlMjI1YzE5LTE5NDMtNGNjMi1iYTdjLTM1MzdmZDA1M2E4MyIsImNsaWVudF9pZCI6Im1lcnJ5eW91In0.lKHgXd2HSPCp2cK6S-ZvLUwXRjnXEX9wryDWV4CmSGw\",\"expires_in\":7199,\"scope\":\"all\",\"blog\":\"https://longfeizheng.github.io/\",\"jti\":\"33951495-0c8f-4d95-bd62-01212accd55d\"}"

效果如下:

刷新令牌:

refresh_token:在用户无感知的情况下得到一个新的令牌,做到刷新令牌的效果
Spring Security OAuth2整合JWT_第6张图片
Spring Security OAuth2整合JWT_第7张图片

你可能感兴趣的:(security,Spring,Security,OAuth2整合JWT)