SpringBoot集成SpringSecurity5和OAuth2 — 5、OAuth2集成JWT

17 篇文章 0 订阅

关于JWT是什么等有关内容本文不做介绍,只讲解OAuth2如何集成JWT,JWT的加密解密等。

本次代码是在SpringBoot集成SpringSecurity5和OAuth2 — 3、基于数据库的OAuth2认证服务器基础上完成的

一、OAuth2集成JWT

1.1加入 spring-security-jwt依赖

<!-- spring-security-jwt -->
<dependency>
   <groupId>org.springframework.security</groupId>
   <artifactId>spring-security-jwt</artifactId>
   <version>1.1.1.RELEASE</version>
</dependency>

1.2 新建一个JWT的配置类JwtConfig

package cn.iotspider.oauth2jdbc.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
public class JwtConfig {

    /**
     * 生成具有jwt的token
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        //设置JWT使用签名
//        accessTokenConverter.setSigningKey("iot_OAuth2_key");
        accessTokenConverter.setSigningKey("29568a368d5eff3ff");
        return accessTokenConverter;
    }

    /**
     * token存储在jwt中
     */
    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * 存储了扩展的JWT信息
     * @return
     */
    @Bean
    public Oauth2JwtTokenEnhancer oauth2JwtTokenEnhancer() {
        return new Oauth2JwtTokenEnhancer();
    }

}

1.3 新建一个Oauth2JwtTokenEnhancer用于扩展JWT中的信息

package cn.iotspider.oauth2jdbc.config;

import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;

import java.util.HashMap;
import java.util.Map;

public class Oauth2JwtTokenEnhancer implements TokenEnhancer {

    /**
     * 扩展JWT中的内容,oAuth2Authentication中存储有登陆成功后的用户信息
     * @param oAuth2AccessToken
     * @param oAuth2Authentication
     * @return
     */
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
        Map<String, Object> info = new HashMap<>();
        //扩展JWT内容
        info.put("Oauth2扩展信息", "Oauth2认证服务器");
        ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info);
        return oAuth2AccessToken;
    }
}

1.4 在认证服务器中Oauth2AuthenticationServer类加入 TokenStore、JwtAccessTokenConverter、Oauth2JwtTokenEnhancer

@Autowired
private TokenStore tokenStore;

@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;

@Autowired
private Oauth2JwtTokenEnhancer oauth2JwtTokenEnhancer;

1.5 改造configure方法中的内容

    /**
     * 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
//        endpoints
//                .tokenStore(tokenStore())
//                .tokenStore(tokenStore) //设置将token存储在JWT中
//                .accessTokenConverter(jwtAccessTokenConverter()) //设置access_token转换器
//                .userDetailsService(userDetailsService)
                //reuseRefreshTokens设置为false时,每次通过refresh_token获得access_token时,
                // 也会刷新refresh_token;也就是说,会返回全新的access_token与refresh_token。
                //默认值是true,只返回新的access_token,refresh_token不变。
//                .reuseRefreshTokens(true)
                // 允许 GET、POST 请求获取 token,即访问端点:oauth/token
//                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);

        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> enhancerList = new ArrayList<>();
        enhancerList.add(oauth2JwtTokenEnhancer); //配置JWT的内容增强器
        enhancerList.add(jwtAccessTokenConverter);
        enhancerChain.setTokenEnhancers(enhancerList);

        endpoints.accessTokenConverter(jwtAccessTokenConverter)
                .tokenStore(tokenStore) //设置将token存储在JWT中
                .userDetailsService(userDetailsService)
                .reuseRefreshTokens(true)
                // 允许 GET、POST 请求获取 token,即访问端点:oauth/token
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                .tokenEnhancer(enhancerChain);

        /*
         * 默认获取token的路径是/oauth/token,通过pathMapping方法,可改变默认路径
         * pathMapping用来配置端点URL链接,有两个参数,都将以 "/" 字符为开始的字符串
         * defaultPath:这个端点URL的默认链接
         * customPath:你要进行替代的URL链接
         */
        endpoints.pathMapping("/oauth/token", "/oauth/token");
    }

1.6 完整的Oauth2AuthenticationServer代码:

package cn.iotspider.oauth2jdbc.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;

@Configuration
@EnableAuthorizationServer
public class Oauth2AuthenticationServer extends AuthorizationServerConfigurerAdapter {

    @Autowired
    public DataSource dataSource;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired
    private Oauth2JwtTokenEnhancer oauth2JwtTokenEnhancer;

//    @Bean
//    public TokenStore tokenStore() {
//        //token默认保存在内存中(也可以保存在数据库、Redis中)。
//        //如果保存在中间件(数据库、Redis),那么资源服务器与认证服务器可以不在同一个工程中。
//        //注意:如果不保存access_token,则没法通过access_token取得用户信息
//        return new JdbcTokenStore(dataSource);
//    }

    @Bean
    public ClientDetailsService jdbcClientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    /**
     * 配置客户端
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(jdbcClientDetailsService());
    }

    /**
     * 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
//        endpoints
//                .tokenStore(tokenStore())
//                .tokenStore(tokenStore) //设置将token存储在JWT中
//                .accessTokenConverter(jwtAccessTokenConverter()) //设置access_token转换器
//                .userDetailsService(userDetailsService)
                //reuseRefreshTokens设置为false时,每次通过refresh_token获得access_token时,
                // 也会刷新refresh_token;也就是说,会返回全新的access_token与refresh_token。
                //默认值是true,只返回新的access_token,refresh_token不变。
//                .reuseRefreshTokens(true)
                // 允许 GET、POST 请求获取 token,即访问端点:oauth/token
//                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);

        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> enhancerList = new ArrayList<>();
        enhancerList.add(oauth2JwtTokenEnhancer); //配置JWT的内容增强器
        enhancerList.add(jwtAccessTokenConverter);
        enhancerChain.setTokenEnhancers(enhancerList);

        endpoints.accessTokenConverter(jwtAccessTokenConverter)
                .tokenStore(tokenStore) //设置将token存储在JWT中
                .userDetailsService(userDetailsService)
                .reuseRefreshTokens(true)
                // 允许 GET、POST 请求获取 token,即访问端点:oauth/token
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                .tokenEnhancer(enhancerChain);

        /*
         * 默认获取token的路径是/oauth/token,通过pathMapping方法,可改变默认路径
         * pathMapping用来配置端点URL链接,有两个参数,都将以 "/" 字符为开始的字符串
         * defaultPath:这个端点URL的默认链接
         * customPath:你要进行替代的URL链接
         */
        endpoints.pathMapping("/oauth/token", "/oauth/token");
    }

    /**
     * 配置资源服务器向认证服务器请求及验证token的规则:默认允许获取token,但是需要授权后才能获取到
     *过来验令牌有效性的请求,不是谁都能验的,必须要是经过身份认证的。
     * 所谓身份认证就是,必须携带clientId,clientSecret,否则随便一请求过来验token是不验的
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//        security.tokenKeyAccess("permitAll()").checkTokenAccess(
//                "isAuthenticated()");

        //默认允许获取token,但是需要授权后才能获取到 ,所以可以去掉 tokenKeyAccess()
        security.checkTokenAccess("isAuthenticated()");
        //允许客户端使用表单方式发送请求token的认证(因为表单一般是POST请求,所以使用POST方式发送获取token,但必须携带clientId,clientSecret,否则随便一请求过来验token是不验的)
        security.allowFormAuthenticationForClients();
    }

}



二、测试

2.1请求token:

http://localhost:8080/oauth/token?grant_type=authorization_code&code=jG5vBP&client_id=client&client_secret=secret&redirect_uri=http://localhost:8090/callback

可以看到除了基本的信息,还返回了 Oauth2扩展信息:"Oauth2认证服务器",这个就是我在JWT中扩展的信息。

————————————————————————————————————————————————————

解析JWT,代码是在oauth2resource(资源服务器模块)

1、加入 spring-security-jwt依赖和jjwt依赖

<!-- spring-security-jwt -->
<dependency>
   <groupId>org.springframework.security</groupId>
   <artifactId>spring-security-jwt</artifactId>
   <version>1.1.1.RELEASE</version>
</dependency>

<!-- jjwt解析JWT -->
<dependency>
   <groupId>io.jsonwebtoken</groupId>
   <artifactId>jjwt</artifactId>
   <version>0.9.1</version>
</dependency>

2、新建JWT的配置类,主要是设置JWT解析的签名,这个签名必须和签发JWT的签名一致;

package cn.iotspider.oauth2resource.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
public class JwtConfig {

    /**
     * 生成具有jwt的token
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        //设置JWT使用签名
//        accessTokenConverter.setSigningKey("iot_OAuth2_key");
        accessTokenConverter.setSigningKey("29568a368d5eff3ff");
        return accessTokenConverter;
    }

    /**
     * token存储在jwt中
     */
    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

}

3、在application.properties中注释掉去认证中心验证token的配置,因为已经将token存储在了JWT中,在配置了JwtConfig后,单独的模块也能自己独立验证token的真实合法性。

4、新建一个请求资源来测试解析并验证JWT信息

@GetMapping("/get")
public Object getCurrentUser(Authentication authentication, HttpServletRequest request) {
    String header = request.getHeader("Authorization");
    String token = header.replace( "Bearer ", "");
    return Jwts.parser()
            .setSigningKey("29568a368d5eff3ff".getBytes(StandardCharsets.UTF_8))
            .parseClaimsJws(token)
            .getBody();
}

注意:setSigningKey()中设置的签名必须和签发JWT使用的一致,否则会解析验证失败。

①:前端的返回信息

②后端控制台报的错误:

io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.

5、解析测试

​​​​​​​

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值