欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring?security?oauth2以redis作為tokenstore及jackson序列化失敗問(wèn)題

 更新時(shí)間:2024年04月15日 10:44:36   作者:紫金丨小飛俠  
這篇文章主要介紹了Spring?security?oauth2以redis作為tokenstore及jackson序列化失敗問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教<BR>

前言

項(xiàng)目當(dāng)中需要用到鑒權(quán)的場(chǎng)景很多,一般會(huì)使用shiro或者spring security作為一個(gè)權(quán)限驗(yàn)證的框架,兩個(gè)框架的優(yōu)缺點(diǎn)這里就不比較了,都是看個(gè)人習(xí)慣。

自己從搭建項(xiàng)目時(shí)就比較傾向于選擇spring全家桶,所以就選擇了spring security + oauth2的模式,一開始是使用jwt(Java-web-token)的方式,沒(méi)別的,因?yàn)檩p,但是慢慢后續(xù)因?yàn)楣δ苌系男枨蟮?,出現(xiàn)了對(duì)token進(jìn)行管理的需求,這才開始啟用redis存儲(chǔ)token。

一、TokenStore

顧名思義就是存儲(chǔ)token和用來(lái)鑒權(quán)的倉(cāng)庫(kù),spring自己實(shí)現(xiàn)了四種方案

內(nèi)存存儲(chǔ),數(shù)據(jù)都是基于內(nèi)存的,項(xiàng)目重啟就沒(méi)了

jdbc存儲(chǔ),管理系統(tǒng)用的比較多,并發(fā)吞吐不高的情況下搓搓有余了,而且坑比較少

jwt,這也就是我之前用的,好處就是token可以攜帶需要的信息,避免二次查詢,記住不要存放敏感信息,而且RSA非對(duì)稱加密的安全性也夠了,缺點(diǎn)就是無(wú)法主動(dòng)失效

我們今天要看的redis存儲(chǔ),其實(shí)和jdbc一樣,區(qū)別在于,我速度快,哈哈哈哈

二、步驟

1.配置和代碼

1.1環(huán)境

  • spring boot 2.0.9.RELEASE
  • redis 5.0.6 集群
  • mysql 8.0 + druid連接池 + mybatis

我這里用了spring cloud alibaba,nacos作為服務(wù)注冊(cè)中心和配置中心了,這個(gè)不影響

  • 授權(quán)服務(wù)器
<dependency>
<!-- 指明版本,解決redis存儲(chǔ)出現(xiàn)的問(wèn)題:java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V問(wèn)題 -->
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.3.RELEASE</version>   
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
  • 資源服務(wù)器
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

添加spring security 和 spring data redis的依賴

1.2配置文件

  • 1.2.1 授權(quán)服務(wù)器配置文件
spring:
  application:
    name: karl-auth-server
  profiles:
    active: dev
  cloud:
    nacos:
      discovery:
        server-addr: ip:8848
        namespace: public
      config:
        server-addr: ip:8848
        file-extension: yaml
        namespace: public
        group: DEFAULT_GROUP
  datasource:
    druid:
      url: jdbc:mysql://ip/database?characterEncoding=utf8&useUnicode=true&useSSL=false
      username: username
      password: password
      driver-class-name: com.mysql.cj.jdbc.Driver
      initial-size: 10
      max-active: 200
      min-idle: 5
      max-wait: 60000
      pool-prepared-statements: false
      max-pool-prepared-statement-per-connection-size: 20
      validation-query: SELECT 1 FROM DUAL
      validation-query-timeout: 30000
      test-on-borrow: false
      test-on-return: false
      test-while-idle: true
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      filters: stat,wall,slf4j
      connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;config.decrypt=true;config.decrypt.key=xxxxxx;
      filter:
        config:
          enabled: true
  cache:
    type: redis
  redis:
    cluster:
      nodes:
        - ip:7001
        - ip:7002
        - ip:7003
        - ip:7004
        - ip:7005
        - ip:7006
      max-redirects: 5
    database: 0
    password: redis的密碼
    timeout: 3000
    jedis:
      pool:
        min-idle: 0
        max-wait: -1
        max-idle: 30
        max-active: 10

mybatis:
  check-config-location: true

server:
  port: 8888
  • 1.2.2 資源服務(wù)器配置文件
spring:
  application:
    name: service-purchase
  #  profiles:
  #    active: dev
  cloud:
    nacos:
      discovery:
        server-addr: ip:8848
        namespace: public
      config:
        server-addr: ip:8848
        file-extension: yaml
        namespace: public
        group: DEFAULT_GROUP
  datasource:
    druid:
      url: jdbc:mysql://ip/databse?characterEncoding=utf8&useUnicode=true&useSSL=false
      username: root
      password: password
      driver-class-name: com.mysql.cj.jdbc.Driver
      initial-size: 10
      max-active: 200
      min-idle: 5
      max-wait: 60000
      pool-prepared-statements: false
      max-pool-prepared-statement-per-connection-size: 20
      validation-query: SELECT 1 FROM DUAL
      validation-query-timeout: 30000
      test-on-borrow: false
      test-on-return: false
      test-while-idle: true
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      filters: stat,wall,slf4j
      connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;config.decrypt=true;config.decrypt.key=xxxxx
      filter:
        config:
          enabled: true
  cache:
    type: redis
  redis:
    cluster:
      nodes:
        - ip:7001
        - ip:7002
        - ip:7003
        - ip:7004
        - ip:7005
        - ip:7006
      max-redirects: 5
    database: 0
    password: redis的密碼
    timeout: 3000
    jedis:
      pool:
        min-idle: 0
        max-wait: -1
        max-idle: 30
        max-active: 10

mybatis:
  mapper-locations: classpath:mapper/**/*Mapper.xml
  check-config-location: true

server:
  port: 8088

1.3 java代碼

  • 1.3.1 授權(quán)服務(wù)器代碼

首先是授權(quán)服務(wù)器的配置

@Configuration
@EnableAuthorizationServer
public class AuthServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    public RedisConnectionFactory redisConnectionFactory;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private CustomUserDetailsServiceImpl customUserDetailsServiceImpl;

    @Bean
    public JdbcClientDetailsService customClientDetailsService() {
        JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        clientDetailsService.setPasswordEncoder(PwdUtils.ENCODER);
        return clientDetailsService;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(customClientDetailsService());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager).userDetailsService(customUserDetailsServiceImpl).tokenStore(tokenStore());
        //配置TokenService參數(shù)
        DefaultTokenServices tokenService = new DefaultTokenServices();
        tokenService.setTokenStore(endpoints.getTokenStore());
        tokenService.setSupportRefreshToken(true);
        tokenService.setClientDetailsService(endpoints.getClientDetailsService());
        tokenService.setTokenEnhancer(endpoints.getTokenEnhancer());
        //token有效期 1小時(shí)
        tokenService.setAccessTokenValiditySeconds(3600);
        //token刷新有效期 15天
        tokenService.setRefreshTokenValiditySeconds(3600 * 12 * 15);
        tokenService.setReuseRefreshToken(false);
        endpoints.tokenServices(tokenService);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security.tokenKeyAccess("isAuthenticated()")
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients(); //允許接口/oauth/check_token 被調(diào)用
    }

    @Bean
    public TokenStore tokenStore() {
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        redisTokenStore.setPrefix("karl-auth-token:");
        //自定義了jackson的序列化策略,沒(méi)搞定
        //redisTokenStore.setSerializationStrategy(new Oauth2JsonSerializationStrategy());
        //JdbcTokenStore jdbcTokenStore = new JdbcTokenStore(dataSource);
        return redisTokenStore;
    }


}
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsServiceImpl customUserDetailsServiceImpl;

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
    //放開 /oauth/** 端點(diǎn)
        http.csrf().disable()
                .authorizeRequests().antMatchers("/oauth/**").permitAll()
                .anyRequest().authenticated()
                .and().httpBasic();
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();

    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserDetailsServiceImpl).passwordEncoder(passwordEncoder());
    }
}
@Component
public class CustomUserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private SysUserMapper sysUserMapper;

    /**
     * 重寫security的查詢方法 這里需要返回username和加密后的password
     **/
    @Override
    public SysUser loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = sysUserMapper.selectByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("username not found:" + username);
        }
        List<SysAuth> authorities = new ArrayList<>();
        authorities.add(new SysAuth("20200202","customer","customer"));
        user.setAuthorities(authorities);
        return user;
    }
}
  • 1.3.2 資源服務(wù)器代碼
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class Oauth2ResourceConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        //關(guān)閉iframe校驗(yàn)
        http.headers().frameOptions().disable();
        //登陸 驗(yàn)證碼 swagger接口及js文件
        http.csrf().disable().authorizeRequests()
                .antMatchers("/actuator/**").permitAll()
                .antMatchers("/**").authenticated();
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        //無(wú)狀態(tài)
        resources.stateless(true).tokenStore(tokenStore());
    }

    /**
     * 設(shè)置token存儲(chǔ),這一點(diǎn)配置要與授權(quán)服務(wù)器相一致
     */
    @Bean
    public RedisTokenStore tokenStore() {
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        //自定義了jackson的序列化策略,沒(méi)搞定
        //redisTokenStore.setSerializationStrategy(new Oauth2JsonSerializationStrategy());
        //redis key前綴
        redisTokenStore.setPrefix("karl-auth-token:");
        return redisTokenStore;
    }

2.測(cè)試

我這邊用了mysql存儲(chǔ)client信息,配置了密碼和授權(quán)碼的模式,這里用密碼的方式測(cè)試

請(qǐng)求token,basic后面的是username:password的base64編碼

curl --location --request POST 'http://127.0.0.1:8888/oauth/token?username=karl&password=karl&grant_type=password' \
--header 'Authorization: Basic Y2xpZW50LUE6a2FybA=='

獲取到的結(jié)果是

{
    "access_token": "56526c6f-abcb-41c6-bb35-812a76e2a049",
    "token_type": "bearer",
    "refresh_token": "ac2e0962-e806-4549-af67-18edc1990d5a",
    "expires_in": 14399,
    "scope": "cuckoo-service"
}

接下來(lái)就可以帶著token去訪問(wèn)資源服務(wù)器的資源了

curl --location --request GET 'http://127.0.0.1:8000/goods?access_token=56526c6f-abcb-41c6-bb35-812a76e2a049'

總結(jié)

可以看到redistoken這里默認(rèn)用的是jdk的序列化策略,spring也提供了1.0和2.0版本的jackson序列化策略,如下

這里折騰了很久,最后寫了一個(gè)策略類,也就是被我注釋掉的那行代碼,最開始各種找序列化策略去重寫,最后發(fā)現(xiàn)自己用jackson手動(dòng)去實(shí)現(xiàn)serializeInternal是沒(méi)問(wèn)題的,但是,這里反序列化會(huì)有問(wèn)題,因?yàn)?code>OAuth2Authentication是沒(méi)有無(wú)參構(gòu)造方法的,所以jackson沒(méi)法實(shí)現(xiàn)反序列化。

public class Oauth2JsonSerializationStrategy extends StandardStringSerializationStrategy {
    @Override
    protected <T> T deserializeInternal(byte[] bytes, Class<T> clazz) {
        return JsonUtils.parse(new String(bytes, StandardCharsets.UTF_8), clazz);
    }

    @Override
    protected byte[] serializeInternal(Object object) {
        return Objects.requireNonNull(JsonUtils.convert(object)).getBytes();
    }
}
@Slf4j
public class JsonUtils {
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    
    public static <T> T parse(String json, Class<T> clazz) {
        try {
            return OBJECT_MAPPER.readValue(json, clazz);
        } catch (IOException e) {
            log.error("jackson 字符串轉(zhuǎn)json失?。簕}", e.getMessage());
        }
        return null;
    }

    public static String convert(Object data) {
        try {
            return OBJECT_MAPPER.writeValueAsString(data);
        } catch (JsonProcessingException e) {
            log.error("jackson json轉(zhuǎn)字符串失敗:{}", e.getMessage());
        }
        return null;
    }
}

我用fastjson也嘗試過(guò),也或多或少有些小問(wèn)題,暫時(shí)采用默認(rèn)的jdk序列化策略,折騰了兩天時(shí)間也算跟了不少源碼,都是自己琢磨出來(lái)的,還是有收獲的。

網(wǎng)上看有人是重寫序列化策略,這種方案應(yīng)該是可行的,等后面找到更好的方案再更新本帖

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java?CAS與Atomic原子操作核心原理詳解

    Java?CAS與Atomic原子操作核心原理詳解

    CAS(Compare?and?Swap)和Atomic原子操作是保證多線程并發(fā)安全的常用機(jī)制,能夠高效地實(shí)現(xiàn)對(duì)共享變量的安全訪問(wèn)和修改,避免線程競(jìng)爭(zhēng)導(dǎo)致的數(shù)據(jù)不一致和死鎖等問(wèn)題。它們的應(yīng)用可以提高程序的并發(fā)性能和可維護(hù)性,是多線程編程中的重要工具
    2023-04-04
  • MyBatis-Plus解決邏輯刪除與唯一索引的問(wèn)題

    MyBatis-Plus解決邏輯刪除與唯一索引的問(wèn)題

    本文主要介紹了MyBatis-Plus解決邏輯刪除與唯一索引的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • Java中的Timer與TimerTask原理詳解

    Java中的Timer與TimerTask原理詳解

    這篇文章主要介紹了Java中的Timer與TimerTask原理詳解,timerTask本身沒(méi)什么意義,只是和timer集合操作的一個(gè)對(duì)象,實(shí)現(xiàn)它就必然有對(duì)應(yīng)的run方法,以被調(diào)用,他甚至于根本不需要實(shí)現(xiàn)Runnable,需要的朋友可以參考下
    2023-07-07
  • 支持生產(chǎn)阻塞的Java線程池

    支持生產(chǎn)阻塞的Java線程池

    在各種并發(fā)編程模型中,生產(chǎn)者-消費(fèi)者模式大概是最常用的了。在實(shí)際工作中,對(duì)于生產(chǎn)消費(fèi)的速度,通常需要做一下權(quán)衡
    2014-04-04
  • Java編程實(shí)現(xiàn)從尾到頭打印鏈表代碼實(shí)例

    Java編程實(shí)現(xiàn)從尾到頭打印鏈表代碼實(shí)例

    這篇文章主要介紹了Java編程實(shí)現(xiàn)從尾到頭打印鏈表代碼實(shí)例,小編覺(jué)得挺不錯(cuò)的,這里分享給大家,供需要的朋友參考。
    2017-10-10
  • Java結(jié)合redistemplate使用分布式鎖案例講解

    Java結(jié)合redistemplate使用分布式鎖案例講解

    在Java中使用RedisTemplate結(jié)合Redis來(lái)實(shí)現(xiàn)分布式鎖是一種常見(jiàn)的做法,特別適用于微服務(wù)架構(gòu)或多實(shí)例部署的應(yīng)用程序中,以確保數(shù)據(jù)的一致性和避免競(jìng)態(tài)條件,下面給大家分享使用Spring Boot和RedisTemplate實(shí)現(xiàn)分布式鎖的案例,感興趣的朋友一起看看吧
    2024-08-08
  • Java8中AbstractExecutorService與FutureTask源碼詳解

    Java8中AbstractExecutorService與FutureTask源碼詳解

    這篇文章主要給大家介紹了關(guān)于Java8中AbstractExecutorService與FutureTask的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2022-01-01
  • SpringBoot文件上傳(本地存儲(chǔ))回顯前端操作方法

    SpringBoot文件上傳(本地存儲(chǔ))回顯前端操作方法

    這篇文章主要介紹了SpringBoot文件上傳(本地存儲(chǔ))回顯前端操作方法的相關(guān)資料,文中講解了文件上傳的基本原理,包括前端調(diào)用后端接口上傳文件,后端返回文件路徑給前端,前端通過(guò)路徑訪問(wèn)圖片,需要的朋友可以參考下
    2024-11-11
  • 2020Mac M1安裝jdk和IDEA的詳細(xì)方法

    2020Mac M1安裝jdk和IDEA的詳細(xì)方法

    這篇文章主要介紹了2020Mac M1安裝jdk和IDEA的詳細(xì)方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-03-03
  • springboot+thymeleaf+druid+mybatis 多模塊實(shí)現(xiàn)用戶登錄功能

    springboot+thymeleaf+druid+mybatis 多模塊實(shí)現(xiàn)用戶登錄功能

    這篇文章主要介紹了springboot+thymeleaf+druid+mybatis 多模塊實(shí)現(xiàn)用戶登錄功能,本文通過(guò)示例代碼圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-07-07

最新評(píng)論