Spring Security OAuth2使用Redis作为token存储

 

Spring Security OAuth2使用Redis作为token存储

授权application.yml 服务器保存token到Redis

server:
  port: 8080

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123
    database: 0 #也可以在代码中指定

Maven依赖

      <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>

 

在spring security oauth2中,授权服务使用redis存储token的时候,报错:

java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V

这说明版本有问题,解决方案是,将oauth2的版本升级到2.3.3,即在pom文件中,加入:

<!-- oauth2 start -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <!-- 指明版本,解决redis存储出现的问题:java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V问题 -->
    <version>2.3.3.RELEASE</version>
</dependency>
<!-- oauth2 end -->

代码分为认证端和客户端两个服务

认证端,也就是我的security服务

有两个文件,一个配置问津,一个

1.AuthResourcesConfig

package com.adao.security.config;

import com.adao.security.common.AuthResourcesConfig;
import com.adao.security.service.SSOUserDetailsService;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

/** * @author * @version 1.0 * @date 2021/8/12 * @Description: 资源服务信息公共配置类 */ public class AuthResourcesConfig { /** * token过期时间,单位为秒 */ public static final int TOKEN_SECONDS_ACCESS = 10 * 60; /** * 刷新token时间,单位为秒 */ public static final int TOKEN_SECONDS_REFRESH = 10 * 60; /** * 资源服务客户端ID信息 */ //rtp public static final String CLIENT_RTP = "RTP"; //manage public static final String CLIENT_RTP_MANAGE = "adao-rtp-manage"; /** * 资源节点对应的密钥,目前统一为 adao */ public static final String CLIENT_RTP_SECRET = "adao"; }

2.AuthorizationServerConfig

package com.adao.security.config;

import com.adao.security.common.AuthResourcesConfig;
import com.adao.security.service.SSOUserDetailsService;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;


/**
 * @author adao
 * @version 1.0
 * @Description: 认证服务器配置
 * @date 2021/8/11
 */
@Configuration
@EnableAuthorizationServer
@Log4j2
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * 鉴权模式
     */
    public static final String GRANT_TYPE[] = {"password", "refresh_token"};

    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 用户服务
     */
    @Autowired
    public SSOUserDetailsService userDetailsService;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * Redis数据库存
     */
    public static final int REDIS_CONNECTION_DATABASE = 14;

    /**
     * 客户端信息配置,可配置多个客户端,可以使用配置文件进行代替
     *
     * @param clients 客户端设置
     * @throws Exception 异常
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 定义客户端应用的通行证
        clients.inMemory()
                // rtp-manage
                .withClient(AuthResourcesConfig.CLIENT_RTP_MANAGE)
                .secret(new BCryptPasswordEncoder().encode(AuthResourcesConfig.CLIENT_RTP_SECRET))
                .authorizedGrantTypes(GRANT_TYPE[0], GRANT_TYPE[1])
                .scopes("all")
                .autoApprove(true)

                // rtp
                .and()
                .withClient(AuthResourcesConfig.CLIENT_RTP)
                .secret(new BCryptPasswordEncoder().encode(AuthResourcesConfig.CLIENT_RTP_SECRET))
                .authorizedGrantTypes(GRANT_TYPE[0], GRANT_TYPE[1])
                .scopes("all")
                .autoApprove(true);

    }

    /**
     * 配置端点
     *
     * @param endpoints 端点
     * @throws Exception 异常
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //配置认证管理器
        endpoints.authenticationManager(authenticationManager)
                //配置用户服务
                .userDetailsService(userDetailsService)
                //配置token存储的服务与位置
                .tokenServices(tokenService())
                .tokenStore(redisTokenStore());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        //允许用户访问OAuth2 授权接口
        security.allowFormAuthenticationForClients();
        //允许已授权用户访问 checkToken 接口和获取 token 接口
        security.tokenKeyAccess("permitAll()");
        //允许已授权用户获取 token 接口
        security.checkTokenAccess("permitAll()");
    }

    /**
     * 设置token存储,资源服务器配置与此处相一致
     */
    @Bean
    public RedisTokenStore redisTokenStore() {
        // 指定redis数据库存储token,与业务库区分。
        LettuceConnectionFactory lettuceConnectionFactory = (LettuceConnectionFactory) redisTemplate.getConnectionFactory();
        lettuceConnectionFactory.setDatabase(REDIS_CONNECTION_DATABASE);
        RedisTokenStore redisTokenStore = new RedisTokenStore(lettuceConnectionFactory);
// 也可以用在何种方式,这样用哪个数据库是在配置文件中指定    
//RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
log.info("Oauth2 redis database : [{}]", lettuceConnectionFactory.getDatabase()); //设置redis token存储中的前缀 redisTokenStore.setPrefix("auth-token:"); return redisTokenStore; } @Bean public DefaultTokenServices tokenService() { DefaultTokenServices tokenServices = new DefaultTokenServices(); //配置token存储  tokenServices.setTokenStore(redisTokenStore()); //开启支持refresh_token tokenServices.setSupportRefreshToken(true); //复用refresh_token tokenServices.setReuseRefreshToken(true); //token有效期  tokenServices.setAccessTokenValiditySeconds(AuthResourcesConfig.TOKEN_SECONDS_ACCESS); //refresh_token有效期  tokenServices.setRefreshTokenValiditySeconds(AuthResourcesConfig.TOKEN_SECONDS_REFRESH); return tokenServices; } }

 

接下来是资源端

1.ResourceServerConfig

package com.adao.manage.config;

import com.adao.manage.common.AuthExceptionHandler;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

/**
 * @author adao
 * @version 1.0
 * @date 2021/8/11
 * @description 资源服务配置类
 */
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Log4j2
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private AuthExceptionHandler authExceptionHandler;

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * Redis数据库存
     */
    public static final int REDIS_CONNECTION_DATABASE = 14;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        //状态
        resources.stateless(true)
                .accessDeniedHandler(authExceptionHandler)
                .authenticationEntryPoint(authExceptionHandler);
        //设置token存储
        resources.tokenStore(redisTokenStore());
    }

    /**
     * 设置token存储,这一点配置要与授权服务器相一致
     */
    @Bean
    public RedisTokenStore redisTokenStore() {

        // 指定redis数据库存储token,与业务库区分。
        LettuceConnectionFactory lettuceConnectionFactory = (LettuceConnectionFactory) redisTemplate.getConnectionFactory();
        lettuceConnectionFactory.setDatabase(REDIS_CONNECTION_DATABASE);
        RedisTokenStore redisTokenStore = new RedisTokenStore(lettuceConnectionFactory);
        log.info("Oauth2 redis database : [{}]",lettuceConnectionFactory.getDatabase());

        //设置redis token存储中的前缀
        redisTokenStore.setPrefix("auth-token:");
        return redisTokenStore;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        //请求权限配置
        http.authorizeRequests()
                //下边的路径放行,不需要经过认证
                .antMatchers("/actuator/health").permitAll()
                .antMatchers("/v2/api-docs", "/swagger-resources/configuration/ui",
                        "/swagger-resources", "/swagger-resources/configuration/security",
                        "/swagger-ui.html", "/webjars/**").permitAll()
                //其余接口没有角色限制,但需要经过认证,只要携带token就可以放行
                .antMatchers("/dictionary/**").permitAll()
                .anyRequest()
                .authenticated();
    }
}
AuthExceptionHandler   异常捕获处理类
package com.adao.manage.common;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author adao
 * @version 1.0
 * @date 2021/8/11
 * @Description: 权限不足返回信息处理类
 */
@Component
@Slf4j
public class AuthExceptionHandler implements AuthenticationEntryPoint, AccessDeniedHandler {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {

        Throwable cause = authException.getCause();
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        // CORS "pre-flight" request
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.addHeader("Cache-Control", "no-cache");
        response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        response.addHeader("Access-Control-Max-Age", "1800");
        if (cause instanceof InvalidTokenException) {
            log.error("InvalidTokenException : {}", cause.getMessage());
            //Token无效
            response.getWriter().write(JSON.toJSONString(ApiResult.fail(ApiCode.ACCESS_TOKEN_INVALID)));
        } else {
            log.error("AuthenticationException : NoAuthentication");
            //资源未授权
            response.getWriter().write(JSON.toJSONString(ApiResult.fail(ApiCode.ACCESS_UNAUTHORIZED)));
        }
    }

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.addHeader("Cache-Control", "no-cache");
        response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        response.addHeader("Access-Control-Max-Age", "1800");
        //访问资源的用户权限不足
        log.error("AccessDeniedException : {}", accessDeniedException.getMessage());
        response.getWriter().write(JSON.toJSONString(ApiResult.fail(ApiCode.INSUFFICIENT_PERMISSIONS)));
    }
}

公用的范围code及 含义

package com.adao.manage.common;


public enum ApiCode {

    SUCCESS(200, "操作成功"),

    /**
     * 表示接口调用方异常提示
     */
    ACCESS_TOKEN_INVALID(1001, "access_token 无效"),
    REFRESH_TOKEN_INVALID(1002, "refresh_token 无效"),
    INSUFFICIENT_PERMISSIONS(1003, "该用户权限不足以访问该资源接口"),
    ACCESS_UNAUTHORIZED(1004, "访问此资源需要身份验证"),

   
}

最后开始测试

postman 设置

http://192.168.10.90:6005/oauth/token?grant_type=password&username=zq&password=123456&scope=all

Spring Security OAuth2使用Redis作为token存储

 

 Spring Security OAuth2使用Redis作为token存储

 

 

Spring Security OAuth2使用Redis作为token存储

 

 直接结果可以看到,获取到了token数据,这里就结束了。

 

 

上一篇:vue.js+webpack在一个简单实例中的使用过程demo


下一篇:SpringBoot整合OAuth2