百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

「Spring Boot 集成应用」 OAUTH2统一认证JWT+Redis+增强TOKEN方式

mhr18 2024-12-03 11:42 17 浏览 0 评论

1. 工程搭建

1、创建工程spring-boot-security-oauth2

2、启动类

com.mirson.spring.boot.security.oauth.startup.SecurityOauthApplication

@SpringBootApplication
@ComponentScan(basePackages = {"com.mirson"})
@EnableCaching
public class SecurityOauthApplication {

    public static void main(String[] args) {

        SpringApplication.run(SecurityOauthApplication.class, args);
    }

}

指定扫描路径, 开启缓存注解。

3、MAVEN依赖

POM.XML

<dependencies>
        <!-- Spring Boot Security 安全依赖组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- Spring Boot Oauth2 认证组件 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
        <!-- Spring Boot Web 依赖组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring Boot 自动化缓存依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <!--Spring Boot Data Redis 缓存依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- Apache Commons 工具依赖 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

    </dependencies>

这里的依赖采用spring-cloud的OAUTH2封装来实现的自动化配置, 与spring-boot-starter-oauth2依赖实质是一样, 都是基于Spring-Security封装, 属性配置基本一致, 如果要加入Spring Cloud, 可以直接集成使用。


2. 创建用户对象

定义两个用户对象, 一个是OAUTH的用户认证对象, 一个是我们接口测试的用户对象。

2.1、OAUTH用户认证对象

com.mirson.spring.boot.security.oauth.po.OAuthUser


@Data
public class OAuthUser extends User {

    /**
     * 用户ID标识
     */
    private Integer id;


    /**
     * 创建日期
     */
    private Date createTime;


    public OAuthUser(String account, String password){
        super(account, password, true, true, true, true, Collections.EMPTY_SET);
        this.id = RandomUtils.nextInt(0, 100);
        this.createTime = new Date();
    }


}

2.2、接口测试用户对象

com.mirson.spring.boot.security.oauth.po.User

@Data
public class User {

    /**
     * ID
     */
    private Integer id;

    /**
     * 用户名称
     */
    private String name;

    /**
     * 年龄
     */
    private String age;

    /**
     * 省份
     */
    private String province;


    /**
     * 创建时间
     */
    private Date createDate;

}


2.3、提供资源接口

定义一个获取用户信息的接口, 受资源权限保护。

com.mirson.spring.boot.security.oauth.controller.UserController

@RestController
@RequestMapping("/user")
@Log4j2
public class UserController {

    @RequestMapping("/getUserInfo")
    @ResponseBody
    public User getUserInfo() {
        User user = new User();
        user.setId(0);
        user.setAge("21");
        user.setName("user1");
        user.setCreateDate(new Date());
        return user;
    }

}

4. OAUTH2集成与权限配置

4.1、工程配置

application.yml

# 服务端口
server:
  port: 22619
# 服务名称  
spring:
  application:
    name: security-oauth2

这里我们便于测试, 我们将认证服务和资源服务放置一起,以上配置即可, 不需加任何其他配置。

如果资源服务是独立的, 工程配置需要加入以下信息:

## spring security 配置
security:
  oauth2:
    resource:
      jwt:
        # JWT 密钥信息
        key-value: sign_secret
      # TOKEN验证接口地址  
      token-info-uri: http://127.0.0.1:21619/oauth/check_token
    client:
      # 客户端模式配置
      client-id: demo
      client-secret: 123456
      scope: server
      access-token-uri: http://127.0.0.1:21619/oauth/token
      user-authorization-uri: http://127.0.0.1:21619/oauth/authorize

4.2、 认证服务配置

新建com.mirson.spring.boot.security.oauth.config.AuthorizationServerConfig配置类, 加入以下配置:

Redis配置:

 /**
  * Redis 缓存配置
* @return
   */
  @Bean
  public RedisTemplate<String, Object> stockRedisTemplate() {
      RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
      redisTemplate.setKeySerializer(new StringRedisSerializer());
      redisTemplate.setHashKeySerializer(new StringRedisSerializer());
      redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
      redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
      redisTemplate.setConnectionFactory(redisConnectionFactory);
      return redisTemplate;
  }

设置redis的key, value 序列化方式。

采用Redis存储TOKEN信息

  /**
     * TokenStore实现方式, 采用Redis缓存
     * @return
     */
    @Bean
    public TokenStore redisTokenStore() {
        RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
        tokenStore.setPrefix(OAUTH_PREFIX_KEY);
        tokenStore.setAuthenticationKeyGenerator(new DefaultAuthenticationKeyGenerator() {
            @Override
            public String extractKey(OAuth2Authentication authentication) {
                return super.extractKey(authentication);
            }
        });
        return tokenStore;
    }

统一Redis的Key前缀, 便于维护。

结合Token增强技术

    /**
	 * token增强处理, 支持扩展信息
	 * @return TokenEnhancer
	 */
	@Bean
	public TokenEnhancer tokenEnhancer() {
		return (accessToken, authentication) -> {
		    try {
                if (OAUTH_CLIENT_CREDENTIALS
                        .equals(authentication.getOAuth2Request().getGrantType())) {
                    return accessToken;
                }
                final Map<String, Object> additionalInfo = new HashMap<>(16);
                OAuthUser authUser = (OAuthUser) authentication.getUserAuthentication().getPrincipal();
                if (null != authUser) {
                    // 放入扩展信息
                    additionalInfo.put("oauth_user_id", authUser.getId());
                    additionalInfo.put("oauth_user_date", authUser.getCreateTime());
                    additionalInfo.put("active", true);
                }
                ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            }catch(Exception e) {
		        log.error(e.getMessage(), e);
            }
			return accessToken;
		};
	}

TOKEN增强可以放置额外信息,扩展性强, 更为灵活, 这些信息不会加入JWT中。

JWT TOKEN配置:

    /**
     * JWT TOKEN配置,采用签名密钥
     */
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(OAUTH_SIGN_KEY);
        return converter;
    }

JWT可以支持密钥签名, 还可以通过证书签名方式, 安全性比较高。

采用内存模式对客户端进行验证:

@Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        clients.inMemory()
                .withClient("demo")
                .secret("4QrcOUm6Wau+VuBX8g+IPg==")
                .authorizedGrantTypes("password", "authorization_code");
    }

原始密码为123456, 我们采用了密码编码器, 这里的密码要设置为密文。

认证服务配置:

  /**
     * 认证服务配置
     * @param endpoints
     */
	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        // 自定义token生成方式
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));

		endpoints
				.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
				.tokenStore(redisTokenStore())
                .tokenEnhancer(tokenEnhancerChain)
				.userDetailsService(authCustomUserDetailService)
				.authenticationManager(authenticationManager)
				.reuseRefreshTokens(false);


	}

采用Token链, 申请TOKEN的时候, 不仅返回JWT信息, 还返回TOKEN 增强设置的信息。

4.3、自定义密码编码器

com.mirson.spring.boot.security.oauth.config.AuthPasswordEncoder

/**
 * 自定义密码加密方式
 */
@Component
@Log4j2
public class AuthPasswordEncoder implements PasswordEncoder {

    /**
     * 编码处理
     * @param rawPassword
     * @return
     */
    @Override
    public String encode(CharSequence rawPassword) {
        return rawPassword.toString();
    }

    /**
     * 密码校验判断
     * @param rawPassword
     * @param encodedPassword
     * @return
     */
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        if(rawPassword != null && rawPassword.length() > 0){
            try {
                // 这里通过MD5及B64加密
                String password = EncryptUtil.encryptSigned(rawPassword.toString());
                boolean isMatch= encodedPassword.equals(password);
                if(!isMatch) {
                    log.warn(" password not match!");
                }
                return encodedPassword.equals(password);
            } catch (Exception e) {
               log.error(e.getMessage(), e);
            }
        }
        return false;
    }

}

可以自定义编码, 比如BASE64等, 自定义校验逻辑,能够灵活处理。

4.4、认证服务WEB配置

com.mirson.spring.boot.security.oauth.config.WebSecurityConfiguration


/**
 * 认证相关配置
 */
@Primary
@Order(90)
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService authCustomClientDetailService;

    @Autowired
    private AuthPasswordEncoder authPasswordEncoder;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/user/getUserInfo").authenticated()
                .anyRequest().permitAll()
                .and()
                .csrf()
                .disable()                // 禁用csrf保护(防止伪造攻击)
                .httpBasic().disable();   // 禁用弹出式认证框

    }

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

	@Autowired
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(authCustomClientDetailService).passwordEncoder(authPasswordEncoder);
	}

}

主要看configure方法配置, 设置哪些需要鉴权的接口地址, 禁用csrf保护, 防止无法请求的情况, 禁用弹出式认证框, 防止调用资源接口时出现401错误。

4.5、自定义用户服务接口

com.mirson.spring.boot.security.oauth.config.AuthCustomUserDetailService

@Service("authCustomUserDetailService")
public class AuthCustomUserDetailService implements UserDetailsService {

    @Autowired
    private CacheManager cacheManager;

    @Override
    public UserDetails loadUserByUsername(String userNo) throws UsernameNotFoundException {

        // 查询缓存
        Cache cache = cacheManager.getCache(AuthorizationServerConfig.OAUTH_KEY_USER_DETAILS);
        if (cache != null && cache.get(userNo) != null) {
            return (UserDetails) cache.get(userNo).get();
        }
        // 封装成OAUTH鉴权的用户对象
        UserDetails userDetails = new OAuthUser("admin", "AZICOnu9cyUFFvBp3xi1AA==");
        // 将用户信息放入缓存
        cache.put(userNo, userDetails);
        return userDetails;
    }
}

这里加入了缓存处理, 实际项目当中, 也推荐使用缓存, 提升处理性能。

如果缓存没有用户, 会构造一个新的用户放入缓存, 密码要采用密文。

4.6、资源服务配置

如果有提供受保护的资源服务, 必须要做具体的配置。

新建com.mirson.spring.boot.security.oauth.config.ResourceServerConfiguration

/**
 * 自定义资源服务器配置
 */
@EnableResourceServer
@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

	@Override
	public void configure(HttpSecurity http) throws Exception {
		http.antMatcher("/user/getUserInfo").authorizeRequests().anyRequest().authenticated();
	}
}

设置了一个受保护的资源接口/user/getUserInfo, 其他接口全部放行。

5. 功能验证

5.1、申请TOKEN,获取JWT与TOKEN增强信息

1)设置Basic Auth认证信息

这里填入的是客户端模式的用户名和密码,并非认证用户信息。

2) 设置密码模式的请求参数信息

3) 请求返回结果

可以看到JWT的TOKEN信息, 以及增强设置中的ID和DATE信息。

5.2 、访问受保护的资源接口

1) 请求获取用户信息接口, 如果不传入TOKEN, 会提示无权访问

2) 传入TOKEN访问

能够正常访问接口, 注意, 传入的TOKEN要选择【Bearer Token】方式。

5.3、访问TOKEN信息

请求接口, 传入token参数: http://127.0.0.1:22619/oauth/check_token

可以看到JWT TOKEN的详细信息。

6. 总结

  • 掌握Spring Boot 与Spring Security 和OATUH2的集成用法, 在微服务场景当中,也能够适用, 微服务应用中一般会接入网关进行统一鉴权,也可以由各资源服务再做鉴权处理 ,方式灵活多样, 大家可以再多做实践。
  • 这里用到了OAUTH2的客户端模式和密码模式, 适合微服务场景大用户量的接入,采用Redis缓存, 能够保障较高的性能, JWT附带信息不能过多, 会增加传输资源开销, 加密方式也不能过于复杂, 影响CPU性能, 如果有较多额外信息需要传递, 可以采用TOKEN增强模式, 数据存储在缓存, 不会带来过多的IO开销。在实际项目当中, 要做仔细权衡,能加入缓存地方尽量加入, 保障安全的同时, 也要有较好的性能。
  • 源码下载地址: https://download.csdn.net/download/hxx688/86400104

相关推荐

【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...

Pure Storage推出统一数据管理云平台及新闪存阵列

PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...

对Java学习的10条建议(对java课程的建议)

不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...

SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!

官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...

JDK21有没有什么稳定、简单又强势的特性?

佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...

「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了

在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...

Java面试题及答案最全总结(2025版)

大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...

数据库日常运维工作内容(数据库日常运维 工作内容)

#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...

分布式之系统底层原理(上)(底层分布式技术)

作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...

oracle 死锁了怎么办?kill 进程 直接上干货

1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...

SpringBoot 各种分页查询方式详解(全网最全)

一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...

《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略

《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...

LoadRunner(loadrunner录制不到脚本)

一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...

Redis数据类型介绍(redis 数据类型)

介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...

RMAN备份监控及优化总结(rman备份原理)

今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...

取消回复欢迎 发表评论: