文章目录
在之前的博客我写了
SpringCloud整合spring security+ oauth2+Redis实现认证授权,本文对返回的token实现自定义增强令牌返回结果,以及对于oauth2存在Redis的数据进行解释。
认证授权中心自定义令牌增强
自定义认证端点返回结果
访问oauth/token,oauth2默认返回的授权token信息如下:
如果不自定义可以看到访问oauth/token,默认访问的是TokenEndpoint下的接口
在授权服务中自定义oauth2控制器实现自定义令牌参数返回,代码如下:
packagecom.zjq.oauth2.server.controller;importcom.zjq.commons.model.domain.ResultInfo;importcom.zjq.commons.utils.ResultInfoUtil;importorg.springframework.security.oauth2.common.DefaultOAuth2AccessToken;importorg.springframework.security.oauth2.common.OAuth2AccessToken;importorg.springframework.security.oauth2.provider.endpoint.TokenEndpoint;importorg.springframework.web.HttpRequestMethodNotSupportedException;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;importjavax.annotation.Resource;importjavax.servlet.http.HttpServletRequest;importjava.security.Principal;importjava.util.LinkedHashMap;importjava.util.Map;/**
* Oauth2 控制器
*/@RestController@RequestMapping("oauth")publicclassOAuthController{@ResourceprivateTokenEndpoint tokenEndpoint;@ResourceprivateHttpServletRequest request;@PostMapping("token")publicResultInfopostAccessToken(Principal principal,@RequestParamMap<String,String> parameters)throwsHttpRequestMethodNotSupportedException{returncustom(tokenEndpoint.postAccessToken(principal, parameters).getBody());}/**
* 自定义 Token 返回对象
*
* @param accessToken
* @return
*/privateResultInfocustom(OAuth2AccessToken accessToken){DefaultOAuth2AccessToken token =(DefaultOAuth2AccessToken) accessToken;Map<String,Object> data =newLinkedHashMap(token.getAdditionalInformation());
data.put("accessToken", token.getValue());
data.put("expireIn", token.getExpiresIn());
data.put("scopes", token.getScope());if(token.getRefreshToken()!=null){
data.put("refreshToken", token.getRefreshToken().getValue());}returnResultInfoUtil.buildSuccess(request.getServletPath(), data);}}
登录逻辑调整,增强令牌返回参数
添加登录认证对象:
packagecom.zjq.commons.model.domain;importcn.hutool.core.util.StrUtil;importcom.google.common.collect.Lists;importlombok.Getter;importlombok.Setter;importorg.springframework.security.core.GrantedAuthority;importorg.springframework.security.core.authority.AuthorityUtils;importorg.springframework.security.core.authority.SimpleGrantedAuthority;importorg.springframework.security.core.userdetails.UserDetails;importjava.util.Collection;importjava.util.List;importjava.util.stream.Collectors;importjava.util.stream.Stream;/**
* 登录认证对象
*
* @Author zjq
* @Date 2022/10/12
*/@Getter@SetterpublicclassSignInIdentityimplementsUserDetails{// 主键privateInteger id;// 用户名privateString username;// 昵称privateString nickname;// 密码privateString password;// 手机号privateString phone;// 邮箱privateString email;// 头像privateString avatarUrl;// 角色privateString roles;// 是否有效 0=无效 1=有效privateint isValid;// 角色集合, 不能为空privateList<GrantedAuthority> authorities;// 获取角色信息@OverridepublicCollection<?extendsGrantedAuthority>getAuthorities(){if(StrUtil.isNotBlank(this.roles)){// 获取数据库中的角色信息Lists.newArrayList();this.authorities =Stream.of(this.roles.split(",")).map(role ->{returnnewSimpleGrantedAuthority(role);}).collect(Collectors.toList());}else{// 如果角色为空则设置为 ROLE_USERthis.authorities =AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");}returnthis.authorities;}@OverridepublicStringgetPassword(){returnthis.password;}@OverridepublicbooleanisAccountNonExpired(){returntrue;}@OverridepublicbooleanisAccountNonLocked(){returntrue;}@OverridepublicbooleanisCredentialsNonExpired(){returntrue;}@OverridepublicbooleanisEnabled(){returnthis.isValid ==0?false:true;}}
登录后返回登录认证对象:
@ResourceprivateUsersMapper usersMapper;@OverridepublicUserDetailsloadUserByUsername(String username)throwsUsernameNotFoundException{AssertUtil.isNotEmpty(username,"请输入用户名");Users users = usersMapper.selectByAccountInfo(username);if(users ==null){thrownewUsernameNotFoundException("用户名或密码错误,请重新输入");}// 初始化登录认证对象SignInIdentity signInIdentity =newSignInIdentity();// 拷贝属性BeanUtils.copyProperties(users, signInIdentity);return signInIdentity;}
在授权服务配置类AuthorizationServerConfiguration中增强令牌返回信息:
/**
* 配置授权以及令牌的访问端点和令牌服务
*
* @param endpoints
* @throws Exception
*/@Overridepublicvoidconfigure(AuthorizationServerEndpointsConfigurer endpoints)throwsException{// 认证器
endpoints.authenticationManager(authenticationManager)// 具体登录的方法.userDetailsService(userService)// token 存储的方式:Redis.tokenStore(redisTokenStore)// 令牌增强对象,增强返回的结果.tokenEnhancer((accessToken, authentication)->{// 获取登录用户的信息,然后设置SignInIdentity signInIdentity =(SignInIdentity) authentication.getPrincipal();LinkedHashMap<String,Object> map =newLinkedHashMap<>();
map.put("nickname", signInIdentity.getNickname());
map.put("avatarUrl", signInIdentity.getAvatarUrl());DefaultOAuth2AccessToken token =(DefaultOAuth2AccessToken) accessToken;
token.setAdditionalInformation(map);return token;});}
测试验证
访问请求/oauth/token,可以看到已经返回我们自己需要的认证授权返回结果。
至此,认证授权微服务已经构建完成。
上述已经完成了认证授权中心的搭建。下面继续通过用户微服务访问认证中心实现登录退出。
接下来我们构建一个用户微服务并通过调用授权认证服务实现登录和退出。
用户微服务构建
用户服务相关pom依赖如下:
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>oauth2-demo</artifactId><groupId>com.zjq</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>ms-users</artifactId><dependencies><!-- eureka client --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><!-- spring web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- spring data redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- mybatis --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency><!-- mysql --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- commons 公共项目 --><dependency><groupId>com.zjq</groupId><artifactId>commons</artifactId><version>1.0-SNAPSHOT</version></dependency><!-- 自定义的元数据依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency></dependencies></project>
配置文件内容如下:
server:# 端口port:8082spring:application:# 应用名name: ms-users
# 数据库datasource:driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password:123456url: jdbc:mysql://127.0.0.1:3306/oauth2?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false# Redisredis:port:6379host: localhost
timeout:3000database:1password:123456# swaggerswagger:base-package: com.zjq.oauth2
title: 用户服务API接口文档
# Oauth2 客户端信息oauth2:client:client-id: appId
secret:123456grant_type: password
scope: api
# 配置 Eureka Server 注册中心eureka:instance:prefer-ip-address:trueinstance-id: ${spring.cloud.client.ip-address}:${server.port}client:service-url:defaultZone: http://localhost:8080/eureka/
# oauth2 服务地址service:name:ms-oauth-server: http://ms-oauth2-server/
# Mybatismybatis:configuration:map-underscore-to-camel-case:true# 开启驼峰映射# 指标监控健康检查management:endpoints:web:exposure:include:"*"# 暴露的端点logging:pattern:console:'%d{HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'
配置类构建
oauth2客户端配置类构建:
/**
* oauth2 客户端配置类
* @author zjq
*/@Component@ConfigurationProperties(prefix ="oauth2.client")@Getter@SetterpublicclassOAuth2ClientConfiguration{privateString clientId;privateString secret;privateString grant_type;privateString scope;}
编写redisTemplate相关配置类,调整默认的序列化方式。
编写远程请求配置类:
/**
* Rest 配置类
*/@ConfigurationpublicclassRestTemplateConfiguration{// 负载均衡请求@LoadBalanced@BeanpublicRestTemplaterestTemplate(){returnnewRestTemplate();}}
相关实体类
申请授权返回实体:
/**
* 申请授权返回实体
* @author zjq
*/@Getter@SetterpublicclassOAuthUserInfoimplementsSerializable{privateString nickname;privateString avatarUrl;privateString accessToken;privateString expireIn;privateList<String> scopes;privateString refreshToken;}
登录成功返回实体:
/**
* 登录成功返回实体
* @author zjq
*/@Setter@GetterpublicclassLoginUserInfoimplementsSerializable{privateString nickname;privateString token;privateString avatarUrl;}
登录
登录功能相关代码如下:
/**
* 登录
*
* @param account
* @param password
* @return
*/@GetMapping("signin")publicResultInfosignIn(String account,String password){return userService.signIn(account, password, request.getServletPath());}
/**
* 登录
*
* @param account 帐号:用户名或手机或邮箱
* @param password 密码
* @param path 请求路径
* @return
*/publicResultInfosignIn(String account,String password,String path){// 参数校验AssertUtil.isNotEmpty(account,"请输入登录帐号");AssertUtil.isNotEmpty(password,"请输入登录密码");// 构建请求头HttpHeaders headers =newHttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);// 构建请求体(请求参数)MultiValueMap<String,Object> body =newLinkedMultiValueMap<>();
body.add("username", account);
body.add("password", password);
body.setAll(BeanUtil.beanToMap(clientOAuth2DataConfiguration));HttpEntity<MultiValueMap<String,Object>> entity =newHttpEntity<>(body, headers);// 设置 Authorization
restTemplate.getInterceptors().add(newBasicAuthenticationInterceptor(clientOAuth2DataConfiguration.getClientId(),
clientOAuth2DataConfiguration.getSecret()));// 发送请求ResponseEntity<ResultInfo> result = restTemplate.postForEntity(oauthServerName +"oauth/token", entity,ResultInfo.class);// 处理返回结果AssertUtil.isTrue(result.getStatusCode()!=HttpStatus.OK,"登录失败");ResultInfo resultInfo = result.getBody();if(resultInfo.getCode()!=ApiConstant.SUCCESS_CODE){// 登录失败
resultInfo.setData(resultInfo.getMessage());return resultInfo;}// 这里的 Data 是一个 LinkedHashMap 转成了域对象 OAuthDinerInfoOAuthUserInfo dinerInfo =BeanUtil.fillBeanWithMap((LinkedHashMap) resultInfo.getData(),newOAuthUserInfo(),false);// 根据业务需求返回视图对象LoginUserInfo loginDinerInfo =newLoginUserInfo();
loginDinerInfo.setToken(dinerInfo.getAccessToken());
loginDinerInfo.setAvatarUrl(dinerInfo.getAvatarUrl());
loginDinerInfo.setNickname(dinerInfo.getNickname());returnResultInfoUtil.buildSuccess(path, loginDinerInfo);}
登录验证:
分别启动注册中心,授权认证中心,用户服务,访问http://localhost:8080/,可以看到授权认证中心和用户微服务都已经注册到eureka:
通过接口请求访问http://localhost:8083/user/signin?account=zjq&password=123456,返回如下:
退出登录
退出登录代码如下:
/**
* 安全退出
*
* @param access_token
* @param authorization
* @return
*/@GetMapping("user/logout")publicResultInfologout(String access_token,String authorization){// 判断 access_token 是否为空,为空将 authorization 赋值给 access_tokenif(StringUtils.isBlank(access_token)){
access_token = authorization;}// 判断 authorization 是否为空if(StringUtils.isBlank(access_token)){returnResultInfoUtil.buildSuccess(request.getServletPath(),"退出成功");}// 判断 bearer token 是否为空if(access_token.toLowerCase().contains("bearer ".toLowerCase())){
access_token = access_token.toLowerCase().replace("bearer ","");}// 清除 redis token 信息OAuth2AccessToken oAuth2AccessToken = redisTokenStore.readAccessToken(access_token);if(oAuth2AccessToken !=null){
redisTokenStore.removeAccessToken(oAuth2AccessToken);OAuth2RefreshToken refreshToken = oAuth2AccessToken.getRefreshToken();
redisTokenStore.removeRefreshToken(refreshToken);}returnResultInfoUtil.buildSuccess(request.getServletPath(),"退出成功");}
至此,我们已经完成了用户的登录和退出流程。
本文内容到此结束了,
如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问💬欢迎各位指出。
主页:共饮一杯无的博客汇总👨💻保持热爱,奔赴下一场山海。🏃🏃🏃
版权归原作者 共饮一杯无 所有, 如有侵权,请联系我们删除。