springboot2.0整合OAuth2并使用JWT作为token。

之前实现了Springboot之Security前后端分离登录 刚好这段时间有空,乘机整合下OAuth2。记录下当中遇到的问题和处理方式。

什么是OAuth2?

OAuth 2.0 的一个简单解释

具体代码实现

POM文件

        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>

授权服务器

@Configuration
@EnableAuthorizationServer
public class OAuth2ServerConfiguration extends AuthorizationServerConfigurerAdapter {

    private static final String CLIENT_ID = "client";  //客户端
    private static final String CLIENT_SECRET = "123456";   //secret客户端安全码
    private static final String GRANT_TYPE_PASSWORD = "password";   // 密码模式授权模式
    private static final String AUTHORIZATION_CODE = "authorization_code"; //授权码模式  授权码模式使用到了回调地址,是最为复杂的方式,通常网站中经常出现的微博,qq第三方登录,都会采用这个形式。
    private static final String REFRESH_TOKEN = "refresh_token";  //
    private static final String IMPLICIT = "implicit"; //简化授权模式
    private static final String GRANT_TYPE = "client_credentials";  //客户端模式
    private static final String SCOPE_WEB = "web";   //授权范围  web端
    private static final String SCOPE_IOS = "ios";   //授权范围  ios端
    private static final String SCOPE_ANDROID = "android";
    private static final String SCOPE_BOOT = "boot"; //授权范围  项目名称
    private static final int ACCESS_TOKEN_VALIDITY_SECONDS = 30*24*60*60;       //token 有效时间 一个月
    private static final int REFRESH_TOKEN_VALIDITY_SECONDS = 30*24*60*60;      //刷新token有效时间 一个月
    /**
     * 描述:注入密码加密编码器 进行密码加密
     */
    @Autowired
    BCryptPasswordEncoder passwordEncoder;
    /**
     * 描述:注入用户信息处理类 处理用户账号信息
     */
    @Autowired
    UserDetailsServiceImpl userDetailService;
    /**
     * 描述:注入token生成器  处理token的生成方式
     */
    @Autowired
    TokenStore tokenStore;
    /**
     * 描述: 注入AuthenticationManager管理器
     */
    @Autowired
    AuthenticationManager authenticationManager;
    /**
     * 描述: 注入jwtAccessTokenConverter 增强token
     */
    @Autowired
    JwtAccessTokenConverter jwtAccessTokenConverter;


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        String secret = new BCryptPasswordEncoder().encode(CLIENT_SECRET);  // 用 BCrypt 对密码编码
        //配置客户端信息
        clients.inMemory()  // 使用in-memory存储
                .withClient(CLIENT_ID)    //client_id用来标识客户的Id
                .authorizedGrantTypes(AUTHORIZATION_CODE,GRANT_TYPE, REFRESH_TOKEN,GRANT_TYPE_PASSWORD,IMPLICIT)  //允许授权类型
                .scopes(SCOPE_WEB,SCOPE_IOS,SCOPE_ANDROID,SCOPE_BOOT)  //允许授权范围
                .authorities("ROLE_CLIENT")  //客户端可以使用的权限
                .secret(secret)  //secret客户端安全码
                .autoApprove(true) // 为true 则不会被重定向到授权的页面,也不需要手动给请求授权,直接自动授权成功返回code
                .accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)   //token 时间秒
                .refreshTokenValiditySeconds(REFRESH_TOKEN_VALIDITY_SECONDS);//刷新token 时间 秒
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                // 允许表单登录
                .allowFormAuthenticationForClients()
                // 密码加密编码器
                .passwordEncoder(passwordEncoder)
                // 允许所有的checkToken请求
                .checkTokenAccess("permitAll()");
    }
    /**
     * 配置令牌
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore)
                // 认证管理器 - 在密码模式必须配置
                .authenticationManager(authenticationManager)
                // 自定义校验用户service
                .userDetailsService(userDetailService)
                // 是否能重复使用 refresh_token
                .reuseRefreshTokens(false);
        // 设置令牌增强 JWT 转换
        TokenEnhancerChain enhancer = new TokenEnhancerChain();
        enhancer.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
        endpoints.tokenEnhancer(enhancer);
    }
}

资源服务器

@Configuration
@EnableResourceServer
public class OAuth2ResourceConfiguration extends ResourceServerConfigurerAdapter {
   
    @Autowired
    TokenStore tokenStore;
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).and().csrf().disable();

        // 配置不登录可以访问 - 放行路径配置
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
                .authorizeRequests();
 
        registry.antMatchers("/login","/oauth/**").permitAll();
        registry.anyRequest().authenticated();
    }

}

token处理

@Configuration
public class TokenConfig {
    /** JWT密钥 */
    private String signingKey = "fastboot";

    /**
     * JWT 令牌转换器
     * @return
     */
    @Bean("jwtAccessTokenConverter")
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter jwt = new JwtAccessTokenConverter(){
            /**
             * 用户信息JWT加密
             */
            @Override
            protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;

                UserInfo user = (UserInfo) authentication.getUserAuthentication().getPrincipal();
                Set<String> tokenScope = token.getScope();
                String scopeTemp = " ";
                if(tokenScope!=null&&tokenScope.size()>0){
                    scopeTemp=tokenScope.iterator().next();
                }
                String scope =scopeTemp;
                //将额外的参数信息存入,用于生成token
                Map<String, Object> data = new HashMap<String, Object>(4){{
                    put("userId", user.getUserId());
                    put("username", user.getUsername());
                    put("email", user.getEmail());
                    put("roleDtos",user.getRoleDtos());
                    put("nickName", user.getNickName());
                    put("authorities", user.getAuthorities());
                    put("scope",scope);
                }};
                //自定义TOKEN包含的信息
                token.setAdditionalInformation(data);
                return super.encode(accessToken, authentication);
            }

            /**
             * 用户信息JWT
             */
            @Override
            protected Map<String, Object> decode(String token) {
                //解析请求当中的token  可以在解析后的map当中获取到上面加密的数据信息
                Map<String, Object> decode = super.decode(token);
                Long userId = (Long)decode.get("userId");
                String username = (String)decode.get("username");
                String email = (String)decode.get("email");
                String nickName = (String)decode.get("nickName");
                String scope = (String)decode.get("scope");
                List<GrantedAuthority> grantedAuthorityList=new ArrayList<>();
                //注意这里获取到的权限 虽然数据库存的权限是 "sys:menu:add"  但是这里就变成了"{authority=sys:menu:add}" 所以使用@PreAuthorize("hasAuthority('{authority=sys:menu:add}')")
                List<LinkedHashMap<String,String>> authorities =(List<LinkedHashMap<String,String>>) decode.get("authorities");
                for (LinkedHashMap<String, String> authority : authorities) {
                    SimpleGrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authority.getOrDefault("authority", "N/A"));
                    grantedAuthorityList.add(grantedAuthority);
                }
                UserInfo userInfo =new UserInfo(username,"N/A",userId, grantedAuthorityList);
                userInfo.setNickName(nickName);
                userInfo.setEmail(email);
                //需要将解析出来的用户存入全局当中,不然无法转换成自定义的user类
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userInfo,null, grantedAuthorityList);
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                decode.put("user_name",userInfo);
                return decode;
            }
        };

        jwt.setSigningKey(signingKey);
        return jwt;
    }


    /**
     * 配置 token 如何生成
     * 1. InMemoryTokenStore 基于内存存储
     * 2. JdbcTokenStore 基于数据库存储
     * 3. JwtTokenStore 使用 JWT 存储 该方式可以让资源服务器自己校验令牌的有效性而不必远程连接认证服务器再进行认证
     */
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    public String getSigningKey() {
        return signingKey;
    }

    public void setSigningKey(String signingKey) {
        this.signingKey = signingKey;
    }
}

controller接口

@RestController
@RequestMapping("/oauth")
public class OauthController {
    @Autowired
    TokenEndpoint tokenEndpoint;

    @PostMapping(value = "/token")
    public ResultInfo<OAuth2AccessToken> token(Principal principal, @RequestParam Map<String, String> parameters) throws Exception {
        ResponseEntity<OAuth2AccessToken> accessToken = tokenEndpoint.postAccessToken(principal, parameters);
        OAuth2AccessToken token = accessToken.getBody();
        // TODO 可以考虑将返回的TOKEN信息存入redis或者数据库
        return ResultInfo.success(token);
    }
    @PostMapping("/t1")
    @PreAuthorize("hasAuthority('{authority=sys:menu:add}')")
    public String getDemo(String name){
        if(SecurityContextHolder.getContext() == null) {
            return null;
        }
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        UserInfo userInfo = (UserInfo) authentication.getPrincipal();

//        return ResultInfo.success(userInfo);
        return userInfo.toString();
    }
}

上面基本上就是完整的代码了。其他的类如:UserDetailsServiceImpl,UserInfo 略!!!

遇到的问题

1 token过期时间设置 可以在OAuth2ServerConfiguration 当中设置accessTokenValiditySeconds(秒) 也可以在TokenConfig 里面进行jwt加密的时候进行设置,token.setExpiration(); 设置后会覆盖OAuth2ServerConfiguration 当中的。

2 权限不匹配问题 虽然数据库存的权限是 “sys:menu:add” 但是oauth2取的时候变成了"{authority=sys:menu:add}" 所以使用接口上使用@PreAuthorize(“hasAuthority(’{authority=sys:menu:add}’)”)进行权限匹配。

3 serurity的User类无法转换为自定义的user子类的问题,需要在 JWT解密的时候,重新构建然后存入全局当中。(PS:无法在获取token当中获取到自定义的user子类)

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
基于Spring Boot、OAuth2.0JWT Token鉴权认证开发的后台接口是一种使用现代化技术实现的身份验证和授权机制。下面是关于这种后台接口的一些说明: 首先,Spring Boot是一个基于Spring框架的快速开发框架,提供了简化的配置和自动化的特性,使开发者能够更快速高效地开发后台接口。 OAuth2.0是一种开放标准的授权协议,它允许用户授权第三方应用访问他们在资源拥有者上存储的信息,而不需要将用户名和密码提供给第三方。 JWT Token(JSON Web Token)是一种用于在网络应用间安全传递声明的一种方式。它被用作身份验证和授权的令牌,通过加密并以JSON格式存储信息,确保信息的完整性和安全性。 基于以上技术,我们可以开发出具有强大安全认证能力的后台接口。首先,用户在访问接口时,需要提供他们的身份证明,这可以是用户名和密码。接口服务器会使用OAuth2.0协议进行身份验证,并颁发JWT Token给用户。用户在未来的请求中,可以使用Token进行身份验证,而无需每次都提供用户名和密码。 接口服务器会对JWT Token进行验证,以确保Token的完整性和有效性。如果Token失效或被伪造,访问将被拒绝。如果验证通过,接口服务器会正常处理用户的请求。 使用Spring Boot和OAuth2.0进行开发,可以方便地设置权限和角色。可以根据用户的角色和权限,限制他们对某些资源的访问。 总之,基于Spring Boot、OAuth2.0JWT Token鉴权认证开发的后台接口提供了一种安全可靠的身份验证和授权机制,能够有效保护后台接口的安全性,防止非法访问和数据泄露。这种技术组合在开发现代化的网络应用时非常有用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值