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

 

 

 

 

 

 直接結果可以看到,獲取到了token數據,這里就結束了。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM