Spring Security + OAuth2 - 黑马程序员(7. Spring Security实现分布式系统授权【从头重写】- UAA)学习笔记

本文主要是介绍Spring Security + OAuth2 - 黑马程序员(7. Spring Security实现分布式系统授权【从头重写】- UAA)学习笔记,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上一篇:Spring Security + OAuth2(6. JWT 令牌)

下一篇:Spring Security + OAuth2 (8. Spring Security实现分布式系统授权【从头重写】- Gateway-Order)

文章目录

        • 提示:这一部分使用到的组件和原视频不太一样,所以有很多不一样的地方
  • 1. 需求分析
  • 2. 编写公共模块
  • 3. 重新编写 UAA 模块
    • 3.1. 修改 POM 文件
    • 3.2. 编写配置文件
    • 3.3. 编写主启动类
    • 3.4. 编写业务类
      • 1. 编写需要用到的实体类 DTO
      • 2. 编写 WebSecurityConfig,进行安全配置
      • 3. 配置令牌
      • 4. 编写用户的密码角色的查询
      • 5. 编写对外的接口
  • 4. 测试 UAA 模块

提示:这一部分使用到的组件和原视频不太一样,所以有很多不一样的地方
  • 注册中心 :Naocs
  • 网关:Gateway
  • 因为这两个组件不一样,所以很多配置、过滤器也不一样
  • 如果是想看和原视频一样的代码,那请找其他文章把。
  • 还有就是我也是初学者,有很多落地的东西我也理解的不是很到位,如果有写错的、写的不好的地方欢迎指正。
  • 在这里很多不重要的就不多说了,下面是我的代码地址:https://gitee.com/yuan934672344/demo-spring-security

1. 需求分析

技术方案如下:
在这里插入图片描述
说明:

  1. UAA认证服务负责认证授权。
  2. 所有请求经过 网关到达微服务
  3. 网关负责鉴权客户端以及请求转发
  4. 网关将token解析后传给微服务,微服务进行授权。

2. 编写公共模块

新建 Maven 项目: commons-api
具体内容 略,详情请看代码。
其中包括:

  • 配置文件
  1. Redis 配置文件:RedisConfig
  • 实体类,entity
  1. 编写用户实体类:User
  2. 资源实体类: Resource
  3. 角色实体类: Role
  4. 资源-角色关系实体类:RoleResourceRel
  • 上述实体类对应的 Mapper、Service 文件
  • 工具类 utils
  1. 统一的返回对象:Result
  2. 统一的返回的消息内容:MessageConstant
  3. Redis 工具类:RedisUtil
  4. 对对象等进行判断的工具类:UtilValidate

3. 重新编写 UAA 模块

  • 为和之前区别,包名和之前有所不同
  • 新建 Maven 项目 :uaa-server
  • 文件目录
    在这里插入图片描述

3.1. 修改 POM 文件

  • 略……

3.2. 编写配置文件

  • 因为我有使用到 Nacos Config,所以把配置文件拆分了。
  1. bootstrap.properties
  2. application.yml

3.3. 编写主启动类

  1. UaaMain

3.4. 编写业务类

1. 编写需要用到的实体类 DTO

  1. Oauth2TokenDto,用于封装令牌的相关信息

    @Data
    @EqualsAndHashCode(callSuper = false)
    @Builder
    public class Oauth2TokenDto {/** 访问令牌 */private String token;/** 刷新令牌 */private String refreshToken;/** 访问令牌头前缀  */private String tokenHead;/** 有效时间(秒) */private int expiresIn;
    }
    
  2. SecurityUser,实现 UserDetails

    @Data
    @NoArgsConstructor
    public class SecurityUser implements UserDetails {/** 这里的字段可以按照自己要求自定义,后面可以将这些信息存入 JWT 令牌 *//** ID */private Long id;/** 用户名  */private String username;/** 用户密码 */private String password;/** 用户状态 */private Boolean enabled = true;/** 权限数据 */private Collection<SimpleGrantedAuthority> authorities;// 构造方法public SecurityUser(User user) {this.setId(Long.valueOf(user.getUserId()));this.setUsername(user.getUserName());this.setPassword(user.getPassword());/* authorities 本应该是插入权限信息的,但是权限信息太多了,如果都放进 JWT 令牌的话,生成的 JWT 令牌就会过于长,也会占用很多空间。*//* 所以这里传入角色信息,再将角色对应的权限信息存入 Redis,后面通过该角色信息,从 Redis 中再查询出权限信息,进而进行鉴权操作。 *//*  当然这里说的都是简单情况,如果需要对每一个用户进行单独的分配权限的话,就不能这样了*/if (user.getRole() != null) {authorities = new ArrayList<>();authorities.add(new SimpleGrantedAuthority("["+user.getRoleCode()+"]"));}}/** 下面是实现 UserDetails 接口中的一些方法  */@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authorities;}@Overridepublic String getPassword() {return this.password;}@Overridepublic String getUsername() {return this.username;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return this.enabled;}
    }
    

2. 编写 WebSecurityConfig,进行安全配置

  • WebSecurityConfig
    @Configuration
    @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {/*** 认证管理器*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}/*** 密码编码器*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** 安全拦截机制(最重要)*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll().antMatchers("/rsa/publicKey").permitAll().anyRequest().authenticated().and().formLogin();}
    }
    

3. 配置令牌

  1. 配置 令牌的加密规则

    @Configuration
    public class TokenConfig {//配置 JWT 令牌存储方案@Beanpublic TokenStore tokenStore() {return new JwtTokenStore(accessTokenConverter());}// 配置令牌的加密规则@Beanpublic JwtAccessTokenConverter accessTokenConverter() {JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();jwtAccessTokenConverter.setKeyPair(keyPair());return jwtAccessTokenConverter;}//从classpath下的证书中获取秘钥对@Beanpublic KeyPair keyPair() {org.springframework.security.rsa.crypto.KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());}
    }
    
  2. 配置令牌增强,扩展令牌的内容

    @Component
    public class JwtTokenEnhancer implements TokenEnhancer {@Overridepublic OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {SecurityUser user = (SecurityUser) authentication.getPrincipal();Map<String, Object> info = new HashMap<>();//把用户ID设置到JWT中info.put("id", user.getId());((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);return accessToken;}
    }
    
  3. 配置 OAuth 的相关设置

    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {@Autowiredprivate TokenStore tokenStore;@Autowiredprivate ClientDetailsService clientDetailsService;@Autowiredprivate AuthorizationCodeServices authorizationCodeServices;@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate JwtAccessTokenConverter accessTokenConverter;@Autowiredprivate PasswordEncoder passwordEncoder;@Autowiredprivate JwtTokenEnhancer jwtTokenEnhancer;//通过数据库存取用户信息@Beanpublic ClientDetailsService clientDetailsService(DataSource dataSource) {JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);clientDetailsService.setPasswordEncoder(passwordEncoder);return clientDetailsService;}// 客户端详情服务,也就是配置支持哪些客户端来请求// 这里是配置从数据库获取信息@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.withClientDetails(clientDetailsService);}// 配置授权相关的服务// 用来配置令牌(token)的访问端点 和 管理规则@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints//认证管理器.authenticationManager(authenticationManager)//授权码服务.authorizationCodeServices(authorizationCodeServices)//令牌管理服务.tokenServices(tokenService()).allowedTokenEndpointRequestMethods(HttpMethod.POST);}// 用来配置令牌端点的安全约束@Overridepublic void configure(AuthorizationServerSecurityConfigurer security){security//oauth/token_key是公开.tokenKeyAccess("permitAll()")//oauth/check_token公开.checkTokenAccess("permitAll()")//表单认证(申请令牌).allowFormAuthenticationForClients();}/*** 令牌管理服务相关配置,以及令牌信息的增强*/@Beanpublic AuthorizationServerTokenServices tokenService() {DefaultTokenServices service=new DefaultTokenServices();//支持刷新令牌service.setSupportRefreshToken(true);//令牌存储策略service.setTokenStore(tokenStore);//令牌增强TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();List<TokenEnhancer> delegates = new ArrayList<>();delegates.add(jwtTokenEnhancer);delegates.add(accessTokenConverter);tokenEnhancerChain.setTokenEnhancers(delegates);service.setTokenEnhancer(tokenEnhancerChain);// 令牌默认有效期2小时service.setAccessTokenValiditySeconds(7200);// 刷新令牌默认有效期3天service.setRefreshTokenValiditySeconds(259200);return service;}/*** 设置授权码模式生成的授权码存入数据库* @param dataSource 数据源*/@Beanpublic AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {//设置授权码模式的授权码如何存取return new JdbcAuthorizationCodeServices(dataSource);}
    }
    

4. 编写用户的密码角色的查询

  • SpringDataUserDetailsServiceImpl, 实现 UserDetailsService

    @Service
    @Slf4j
    public class SpringDataUserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate ResourceService resourceService;@Autowiredprivate RoleResourceRelService roleResourceRelService;@Autowiredprivate RoleService roleService;@Autowiredprivate UserService userService;@Autowiredprivate RedisUtil redisUtil;@Autowiredprivate PasswordEncoder passwordEncoder;//根据 账号查询用户信息@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {Boolean isEmailCode = false;if (username.contains("##")) {username = username.replace("##","").trim();isEmailCode = true;}//通过传来的用户名查询用户信息User user = this.getUserByUsername(username);if(UtilValidate.isEmpty(user)){log.info("<< AUTH >> --- 传来的 UserName:"+username+" , 查无此人,抛出异常");//如果用户查不到,返回null,由provider来抛出异常throw new UsernameNotFoundException(MessageConstant.USERNAME_PASSWORD_ERROR);}log.info("<< AUTH >> --- 传来的 UserName:"+username+" , 查询到的结果为:"+user.toString());this.selectAllRoleResourceRel();this.getRoleCode(user);if (isEmailCode){String password = user.getPassword();password = passwordEncoder.encode(password);user.setPassword(password);}return new SecurityUser(user);}/*** 根据账号查询用户信息*/public User getUserByUsername(String username){User user = userService.selectByUserName(username);if (UtilValidate.isNotEmpty(user)) {return user;}return null;}public void selectAllRoleResourceRel(){// 查询出所有的资源信息List<RoleResourceRel> roleResources = roleResourceRelService.list();List<Resource> resources = resourceService.list();HashMap<String, Resource> resourceMap = new HashMap<>();for (Resource resource : resources) {resourceMap.put(String.valueOf(resource.getResourceId()), resource);}// 将 resourceUrl 和 roleId 对应关系存进 RedissetUrlAndRoleIdsRelToRedis(roleResources, resourceMap);// 将 roleId 和 resourceCode 对应关系存进 RedissetRoleIdAndResourceCodeToRedis(roleResources, resourceMap);}public void getRoleCode(User user){if (UtilValidate.isEmpty(user)) {return;}QueryWrapper<Role> wrapper = new QueryWrapper<>();wrapper.eq("name", user.getRole());Role one = roleService.getOne(wrapper);if (UtilValidate.isEmpty(one)) {return;}else {user.setRoleCode(String.valueOf(one.getId()));}}//@Asyncpublic Boolean setUrlAndRoleIdsRelToRedis(List<RoleResourceRel> roleResources,HashMap<String, Resource> resourceMap){Map<Object, Object> roleResourceRel = redisUtil.hmget(Constants.KEY_IN_REDIS.AUTH_RESOURCE_URL_ROLE_IDS_CEL.getValue());if (UtilValidate.isNotEmpty(roleResourceRel)) {log.info("<< AUTH >> --- resourceUrl 和 roleId 关系信息已经存在直接返回");return true;}HashMap<String, List<String>> roleResourceMap = new HashMap<>();for (RoleResourceRel roleResource : roleResources) {Long resourceId = roleResource.getResourceId();Resource resource = resourceMap.get(String.valueOf(resourceId));String key = resource.getResourceMethod() + "-" + resource.getResourceUrl();List<String> list = roleResourceMap.get(key);if (UtilValidate.isEmpty(list)){list = new ArrayList<>();}list.add("["+roleResource.getRoleId()+"]");roleResourceMap.put(key, list);}log.info("<< AUTH >> --- 正在将 (资源URL, 角色ID) 关系信息存入 Redis");boolean hmset = redisUtil.hmset(Constants.KEY_IN_REDIS.AUTH_RESOURCE_URL_ROLE_IDS_CEL.getValue(), roleResourceMap, 12, TimeUnit.HOURS);if (!hmset){// 不成功重试一次log.info("<< AUTH >> --- 出错, 进行重试");hmset = redisUtil.hmset(Constants.KEY_IN_REDIS.AUTH_RESOURCE_URL_ROLE_IDS_CEL.getValue(), roleResourceMap, 12, TimeUnit.HOURS);}log.info("<< AUTH >> --- 成功将 (资源URL, 角色ID) 关系信息存入 Redis");return hmset;}//@Asyncpublic Boolean setRoleIdAndResourceCodeToRedis(List<RoleResourceRel> roleResources,HashMap<String, Resource> resourceMap){Map<Object, Object> roleResourceRel = redisUtil.hmget(Constants.KEY_IN_REDIS.AUTH_ROLE_ID_RESOURCE_CODE_CEL.getValue());if (UtilValidate.isNotEmpty(roleResourceRel)) {log.info("<< AUTH >> --- roleId 和 resourceCode 关系信息已经存在直接返回");return true;}HashMap<String, List<String>> roleResourceMap = new HashMap<>();for (RoleResourceRel roleResource : roleResources) {String key = String.valueOf(roleResource.getRoleId());Long resourceId = roleResource.getResourceId();Resource resource = resourceMap.get(String.valueOf(resourceId));List<String> resources = roleResourceMap.get(key);if (UtilValidate.isEmpty(resources)) {resources = new ArrayList<>();}resources.add(resource.getResourceCode());roleResourceMap.put(key, resources);}log.info("<< AUTH >> --- 正在将 (角色ID, 资源Code) 关系信息存入 Redis");boolean hmset = redisUtil.hmset(Constants.KEY_IN_REDIS.AUTH_ROLE_ID_RESOURCE_CODE_CEL.getValue(), roleResourceMap, 12, TimeUnit.HOURS);if (!hmset){// 不成功重试一次log.info("<< AUTH >> --- 出错, 进行重试");hmset = redisUtil.hmset(Constants.KEY_IN_REDIS.AUTH_ROLE_ID_RESOURCE_CODE_CEL.getValue(), roleResourceMap, 12, TimeUnit.HOURS);}log.info("<< AUTH >> --- 成功将 (角色ID, 资源Code) 关系信息存入 Redis");return hmset;}
    }
    

5. 编写对外的接口

  1. KeyPairController, 编写获取 JWT 令牌加密密钥的接口

    @RestController
    public class KeyPairController {@Autowiredprivate KeyPair keyPair;@GetMapping("/rsa/publicKey")public Map<String, Object> getKey() {RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();RSAKey key = new RSAKey.Builder(publicKey).build();return new JWKSet(key).toJSONObject();}
    }
    
  2. AuthController, 封装获取令牌的接口

    @RestController
    @RequestMapping("/oauth")
    @Slf4j
    public class AuthController {@Autowiredprivate TokenEndpoint tokenEndpoint;/*** Oauth2登录认证*/@PostMapping(value = "/token")public String postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();Oauth2TokenDto oauth2TokenDto = Oauth2TokenDto.builder().token(oAuth2AccessToken.getValue()).refreshToken(oAuth2AccessToken.getRefreshToken().getValue()).expiresIn(oAuth2AccessToken.getExpiresIn()).tokenHead("Bearer ").build();Result<Oauth2TokenDto> result = Result.succeed(oauth2TokenDto);return JSONObject.toJSONString(result);}
    }
    

4. 测试 UAA 模块

  1. 启动 UAA 模块
  2. 启动 PostMan,我是使用 PostMan 进行测试
  3. 访问 :http://localhost:8090/oauth/token?grant_type=password&client_secret=secret&password=123456&username=11111user&client_id=myjob
    在这里插入图片描述
  4. 检验令牌
    在这里插入图片描述
  5. 测试通过,UAA 模块编写完成

这篇关于Spring Security + OAuth2 - 黑马程序员(7. Spring Security实现分布式系统授权【从头重写】- UAA)学习笔记的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/214455

相关文章

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

SpringBoot多环境配置数据读取方式

《SpringBoot多环境配置数据读取方式》SpringBoot通过环境隔离机制,支持properties/yaml/yml多格式配置,结合@Value、Environment和@Configura... 目录一、多环境配置的核心思路二、3种配置文件格式详解2.1 properties格式(传统格式)1.

Apache Ignite 与 Spring Boot 集成详细指南

《ApacheIgnite与SpringBoot集成详细指南》ApacheIgnite官方指南详解如何通过SpringBootStarter扩展实现自动配置,支持厚/轻客户端模式,简化Ign... 目录 一、背景:为什么需要这个集成? 二、两种集成方式(对应两种客户端模型) 三、方式一:自动配置 Thick

Python实现网格交易策略的过程

《Python实现网格交易策略的过程》本文讲解Python网格交易策略,利用ccxt获取加密货币数据及backtrader回测,通过设定网格节点,低买高卖获利,适合震荡行情,下面跟我一起看看我们的第一... 网格交易是一种经典的量化交易策略,其核心思想是在价格上下预设多个“网格”,当价格触发特定网格时执行买

Spring WebClient从入门到精通

《SpringWebClient从入门到精通》本文详解SpringWebClient非阻塞响应式特性及优势,涵盖核心API、实战应用与性能优化,对比RestTemplate,为微服务通信提供高效解决... 目录一、WebClient 概述1.1 为什么选择 WebClient?1.2 WebClient 与

Java.lang.InterruptedException被中止异常的原因及解决方案

《Java.lang.InterruptedException被中止异常的原因及解决方案》Java.lang.InterruptedException是线程被中断时抛出的异常,用于协作停止执行,常见于... 目录报错问题报错原因解决方法Java.lang.InterruptedException 是 Jav

深入浅出SpringBoot WebSocket构建实时应用全面指南

《深入浅出SpringBootWebSocket构建实时应用全面指南》WebSocket是一种在单个TCP连接上进行全双工通信的协议,这篇文章主要为大家详细介绍了SpringBoot如何集成WebS... 目录前言为什么需要 WebSocketWebSocket 是什么Spring Boot 如何简化 We

java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)

《java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)》:本文主要介绍java中pdf模版填充表单踩坑的相关资料,OpenPDF、iText、PDFBox是三... 目录准备Pdf模版方法1:itextpdf7填充表单(1)加入依赖(2)代码(3)遇到的问题方法2:pd

Java Stream流之GroupBy的用法及应用场景

《JavaStream流之GroupBy的用法及应用场景》本教程将详细介绍如何在Java中使用Stream流的groupby方法,包括基本用法和一些常见的实际应用场景,感兴趣的朋友一起看看吧... 目录Java Stream流之GroupBy的用法1. 前言2. 基础概念什么是 GroupBy?Stream

python设置环境变量路径实现过程

《python设置环境变量路径实现过程》本文介绍设置Python路径的多种方法:临时设置(Windows用`set`,Linux/macOS用`export`)、永久设置(系统属性或shell配置文件... 目录设置python路径的方法临时设置环境变量(适用于当前会话)永久设置环境变量(Windows系统