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

相关文章

IDEA运行spring项目时,控制台未出现的解决方案

《IDEA运行spring项目时,控制台未出现的解决方案》文章总结了在使用IDEA运行代码时,控制台未出现的问题和解决方案,问题可能是由于点击图标或重启IDEA后控制台仍未显示,解决方案提供了解决方法... 目录问题分析解决方案总结问题js使用IDEA,点击运行按钮,运行结束,但控制台未出现http://

解决Spring运行时报错:Consider defining a bean of type ‘xxx.xxx.xxx.Xxx‘ in your configuration

《解决Spring运行时报错:Considerdefiningabeanoftype‘xxx.xxx.xxx.Xxx‘inyourconfiguration》该文章主要讲述了在使用S... 目录问题分析解决方案总结问题Description:Parameter 0 of constructor in x

解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题

《解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题》文章详细描述了在使用lombok的@Data注解标注实体类时遇到编译无误但运行时报错的问题,分析... 目录问题分析问题解决方案步骤一步骤二步骤三总结问题使用lombok注解@Data标注实体类,编译时

JSON字符串转成java的Map对象详细步骤

《JSON字符串转成java的Map对象详细步骤》:本文主要介绍如何将JSON字符串转换为Java对象的步骤,包括定义Element类、使用Jackson库解析JSON和添加依赖,文中通过代码介绍... 目录步骤 1: 定义 Element 类步骤 2: 使用 Jackson 库解析 jsON步骤 3: 添

Java中注解与元数据示例详解

《Java中注解与元数据示例详解》Java注解和元数据是编程中重要的概念,用于描述程序元素的属性和用途,:本文主要介绍Java中注解与元数据的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参... 目录一、引言二、元数据的概念2.1 定义2.2 作用三、Java 注解的基础3.1 注解的定义3.2 内

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客

Java中List转Map的几种具体实现方式和特点

《Java中List转Map的几种具体实现方式和特点》:本文主要介绍几种常用的List转Map的方式,包括使用for循环遍历、Java8StreamAPI、ApacheCommonsCollect... 目录前言1、使用for循环遍历:2、Java8 Stream API:3、Apache Commons

JavaScript中的isTrusted属性及其应用场景详解

《JavaScript中的isTrusted属性及其应用场景详解》在现代Web开发中,JavaScript是构建交互式应用的核心语言,随着前端技术的不断发展,开发者需要处理越来越多的复杂场景,例如事件... 目录引言一、问题背景二、isTrusted 属性的来源与作用1. isTrusted 的定义2. 为

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

使用Python实现高效的端口扫描器

《使用Python实现高效的端口扫描器》在网络安全领域,端口扫描是一项基本而重要的技能,通过端口扫描,可以发现目标主机上开放的服务和端口,这对于安全评估、渗透测试等有着不可忽视的作用,本文将介绍如何使... 目录1. 端口扫描的基本原理2. 使用python实现端口扫描2.1 安装必要的库2.2 编写端口扫