功能:在保险、规则、交易、积分和短信模块中实现初始功能和基础组件。

This commit is contained in:
abcv7
2026-03-03 04:04:22 +08:00
parent e050eb3317
commit e156d625bf
1999 changed files with 146080 additions and 63 deletions
@@ -0,0 +1,21 @@
FROM openjdk:11-jdk
LABEL maintainer="研究院研发组 <research@itcast.cn>"
# 时区修改为东八区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
WORKDIR /security-oauth
ARG PACKAGE_PATH=./target/security-oauth.jar
ADD ${PACKAGE_PATH:-./} security-oauth.jar
EXPOSE 8080
ENV JAVA_OPTS="\
-server \
-Xms256m \
-Xmx512m \
-XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=512m\
-Dspring.profiles.active=test"
ENTRYPOINT ["sh","-c","java -Djava.security.egd=file:/dev/./urandom -jar $JAVA_OPTS security-oauth.jar"]
@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.itheima.sfbx</groupId>
<artifactId>sfbx-security</artifactId>
<version>2.0-SNAPSHOT</version>
</parent>
<!--权限处理:认证模块-->
<artifactId>security-oauth</artifactId>
<name>security-oauth</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-mybatis-plus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>security-interface</artifactId>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-web</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.jks</include>
<include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.yaml</include>
<include>**/*.txt</include>
</includes>
</resource>
</resources>
<finalName>security-oauth</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,16 @@
package com.itheima.sfbx.security;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* 前后端都需要使用到的登录认证中心
*/
@SpringBootApplication(scanBasePackages = "com.itheima.sfbx")
@EnableDiscoveryClient
public class SecurityOauthStart {
public static void main(String[] args) {
SpringApplication.run(SecurityOauthStart.class);
}
}
@@ -0,0 +1,22 @@
package com.itheima.sfbx.security.adepter;
import com.itheima.sfbx.framework.commons.dto.security.UserVO;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import java.security.Principal;
import java.util.Map;
/**
* @ClassName LoginAuthAdepter.java
* @Description 登录适配接口
*/
public interface LoginAuthAdepter {
/***
* @description 适配路由
* @param principal 认证主体
* @param parameters 登录参数
* @return
*/
UserVO adepterRoutes(Principal principal, Map<String, String> parameters) throws HttpRequestMethodNotSupportedException;
}
@@ -0,0 +1,61 @@
package com.itheima.sfbx.security.adepter.impl;
import com.itheima.sfbx.framework.commons.constant.security.CompanyCacheConstant;
import com.itheima.sfbx.framework.commons.constant.security.OauthConstant;
import com.itheima.sfbx.framework.commons.dto.security.CompanyVO;
import com.itheima.sfbx.framework.commons.dto.security.UserVO;
import com.itheima.sfbx.framework.commons.enums.security.AuthEnum;
import com.itheima.sfbx.framework.commons.exception.ProjectException;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import com.itheima.sfbx.framework.commons.utils.RegisterBeanHandler;
import com.itheima.sfbx.security.handler.LoginAuthHandler;
import com.itheima.sfbx.security.adepter.LoginAuthAdepter;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
import java.util.Map;
/**
* @ClassName LoginAuthAdepterImpl.java
* @Description 登录适配器
*/
@Component
public class LoginAuthAdepterImpl implements LoginAuthAdepter {
@Autowired
RegisterBeanHandler registerBeanHandler;
@Autowired
RedissonClient redissonClient;
@Autowired
HttpServletRequest httpServletRequest;
@Override
public UserVO adepterRoutes(Principal principal, Map<String, String> parameters)
throws HttpRequestMethodNotSupportedException {
//获得域名
String host = httpServletRequest.getHeaders("x-forwarded-host").nextElement().split(",")[0].split(":")[0];
String key = CompanyCacheConstant.WEBSITE+host;
//域名校验
RBucket<CompanyVO> bucket = redissonClient.getBucket(key);
CompanyVO companyVO = bucket.get();
if (EmptyUtil.isNullOrEmpty(companyVO)){
throw new ProjectException(AuthEnum.HSOT_FAIL);
}
//适配登录方式
String loginType = parameters.get(OauthConstant.LOGIN_TYPE_KEY);
if (EmptyUtil.isNullOrEmpty(loginType)){
throw new ProjectException(AuthEnum.LOGIN_FAIL);
}
String loginBeanName = OauthConstant.loginBeanNames.get(loginType.split("-")[1]);
LoginAuthHandler loginAuthHandler = registerBeanHandler.getBean(loginBeanName,LoginAuthHandler.class);
return loginAuthHandler.loginHandler(principal,parameters,loginBeanName,companyVO);
}
}
@@ -0,0 +1,158 @@
package com.itheima.sfbx.security.base;
import com.itheima.sfbx.framework.commons.dto.security.DataSecurityVO;
import com.itheima.sfbx.framework.commons.dto.security.DeptPostUserVO;
import com.itheima.sfbx.framework.commons.dto.security.UserVO;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
/**
* @ClassName AuthUser.java
* @Description 自定认证用户
*/
@Data
@NoArgsConstructor
public class UserAuth implements UserDetails {
private String id;
//"用户账号
private String username;
//"密码
private String password;
//权限内置
private Collection<SimpleGrantedAuthority> authorities;
//"用户昵称
private String nickName;
//"用户邮箱
private String email;
//"真实姓名
private String realName;
//"手机号码
private String mobile;
//"用户性别(0男 1女 2未知)
private String sex;
//"创建者
private Long createBy;
//"创建时间
private LocalDateTime createTime;
//"更新者
private Long updateBy;
//"更新时间
private LocalDateTime updateTime;
//"备注
private String remark;
//"三方openId
private String openId;
//"查询用户:所属单位职位
private Set<DeptPostUserVO> deptPostUserVOset;
//"构建令牌:用户角色标识
private Set<String> roleLabels;
//"构建令牌:用户权限路径
private Set<String> resourceRequestPaths;
//"部门编号【当前】
private String deptNo;
//"职位编号【当前】
private String postNo;
private String clientId;
private DataSecurityVO dataSecurityVO;
private Boolean onlyAuthenticate ;
private String companyNo;
public UserAuth(UserVO userVO) {
this.setId(userVO.getId().toString());
this.setUsername(userVO.getUsername());
this.setPassword(userVO.getPassword());
if (!EmptyUtil.isNullOrEmpty(userVO.getResourceRequestPaths())) {
authorities = new ArrayList<>();
userVO.getResourceRequestPaths().forEach(resourceRequestPath -> authorities.add(new SimpleGrantedAuthority(resourceRequestPath)));
}
this.setNickName(userVO.getNickName());
this.setEmail(userVO.getEmail());
this.setRealName(userVO.getRealName());
this.setMobile(userVO.getMobile());
this.setSex(userVO.getSex());
this.setCreateTime(userVO.getCreateTime());
this.setCreateBy(userVO.getCreateBy());
this.setUpdateTime(userVO.getUpdateTime());
this.setUpdateBy(userVO.getUpdateBy());
this.setRemark(userVO.getRemark());
this.setOpenId(userVO.getOpenId());
this.setDeptPostUserVOset(userVO.getDeptPostUserVOs());
this.setRoleLabels(userVO.getRoleLabels());
this.setResourceRequestPaths(userVO.getResourceRequestPaths());
this.setDeptNo(userVO.getDeptNo());
this.setPostNo(userVO.getPostNo());
this.setClientId(userVO.getClientId());
this.setDataSecurityVO(userVO.getDataSecurityVO());
this.setOnlyAuthenticate(userVO.getOnlyAuthenticate());
this.setCompanyNo(userVO.getCompanyNo());
this.setRealName(userVO.getRealName());
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
@@ -0,0 +1,220 @@
package com.itheima.sfbx.security.config;
import com.alibaba.fastjson.JSONObject;
import com.itheima.sfbx.framework.commons.basic.ResponseResult;
import com.itheima.sfbx.framework.commons.constant.security.OauthConstant;
import com.itheima.sfbx.framework.commons.enums.security.AuthEnum;
import com.itheima.sfbx.framework.commons.properties.SecurityConfigProperties;
import com.itheima.sfbx.framework.commons.utils.ResponseResultBuild;
import com.itheima.sfbx.security.base.UserAuth;
import com.itheima.sfbx.security.details.JdbcClientDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.sql.DataSource;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 授权服务配置
*/
@Configuration
@EnableAuthorizationServer
@EnableConfigurationProperties(SecurityConfigProperties.class)
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService oauth2UserDetailsService;
@Autowired
SecurityConfigProperties securityConfigProperties;
/***
* @description 客户端信息到数据库中
* @param clients 客户端配置形象
* @return
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(jdbcClientDetailsService());
}
/***
* @description 客户端信息JDBC管理
* @return
*/
@Bean
public JdbcClientDetailsService jdbcClientDetailsService(){
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsServiceImpl(dataSource);
jdbcClientDetailsService.setFindClientDetailsSql(OauthConstant.FIND_CLIENT_DETAILS_SQL);
jdbcClientDetailsService.setSelectClientDetailsSql(OauthConstant.SELECT_CLIENT_DETAILS_SQL);
return jdbcClientDetailsService;
}
/***
* @description 授权服务器端点的非安全特性如令牌存储、令牌自定义、用户批准和授权类型
* @param endpoints 认证服务令牌站点
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)//配置认证管理器
.userDetailsService(oauth2UserDetailsService)//配置oauth2User查询明细
.accessTokenConverter(jwtAccessTokenConverter())//配置令牌转换器
.tokenServices(jwtTokenService())//配置令牌服务(token services)
.reuseRefreshTokens(true);//是否沿用刷新令牌直到过期,这里每次刷新直接后使用新的刷新令牌下次刷新
}
/**
* 令牌服务:持久化令牌相关服务
*/
@Bean
public DefaultTokenServices jwtTokenService() {
DefaultTokenServices service=new DefaultTokenServices();
//存储策略
JwtTokenStore jwtTokenStore = new JwtTokenStore(jwtAccessTokenConverter());
service.setTokenStore(jwtTokenStore);//令牌存储的持久性策略。
service.setClientDetailsService(jdbcClientDetailsService());//指定客戶端明细信息
service.setSupportRefreshToken(true);//是否支持刷新令牌
service.setTokenEnhancer(tokenEnhancerChain());//在将新令牌保存到令牌存储区之前,将应用于新令牌的访问令牌增强器。
service.setAccessTokenValiditySeconds(securityConfigProperties.getAccessTokenValiditySeconds()); // 令牌默认有效期2小时
service.setRefreshTokenValiditySeconds(securityConfigProperties.getRefreshTokenValiditySeconds()); // 刷新令牌默认有效期3天
return service;
}
/***
* @description 复合令牌增强器:把构建的JWT令牌及增强属性填充到OAuth2AccessToken中
*/
@Bean
public TokenEnhancerChain tokenEnhancerChain(){
//token令牌增强者List
List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
//扩展JWT内容增强,可携带更多对象信息:企业号、门店ID等
tokenEnhancers.add(tokenEnhancer());
//使用非对称加密算法对token签名
tokenEnhancers.add(jwtAccessTokenConverter());
//构建增强链:填入token令牌增强者List
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);
return tokenEnhancerChain;
}
/**
* 使用非对称加密算法对token签名
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//从classpath下的密钥库中获取密钥对(公钥+私钥)
converter.setKeyPair(keyPair());
return converter;
}
/**
* 从classpath下的密钥库中获取密钥对(公钥+私钥)
*/
@Bean
public KeyPair keyPair() {
KeyStoreKeyFactory factory = new KeyStoreKeyFactory(
new ClassPathResource("itheima.jks"), "shuwenqi".toCharArray());
KeyPair keyPair = factory.getKeyPair(
"itheima", "shuwenqi".toCharArray());
return keyPair;
}
/**
* JWT内容增强
*/
@Bean
public TokenEnhancer tokenEnhancer() {
return (accessToken, authentication) -> {
Map<String, Object> map = new HashMap<>(10);
Object principal = authentication.getUserAuthentication().getPrincipal();
if (principal instanceof UserAuth){
UserAuth userAuth = (UserAuth)principal ;
map.put(OauthConstant.USER_ID_KEY, userAuth.getId());
map.put(OauthConstant.REAL_NAME_KEY, userAuth.getRealName());
map.put(OauthConstant.CLIENT_ID_KEY, userAuth.getClientId());
map.put(OauthConstant.USER_NAME_KEY,userAuth.getUsername());
map.put(OauthConstant.SEX_KEY,userAuth.getSex());
map.put(OauthConstant.RESOURCS_KEY,userAuth.getResourceRequestPaths());
map.put(OauthConstant.ROLES_KEY,userAuth.getRoleLabels());
map.put(OauthConstant.OPEN_ID_KEY,userAuth.getOpenId());
map.put(OauthConstant.DEPT_NO_KEY,userAuth.getDeptNo());
map.put(OauthConstant.POST_NO_KEY,userAuth.getPostNo());
map.put(OauthConstant.MOBILE_KEY,userAuth.getMobile());
map.put(OauthConstant.ONLY_AUTHENTICATE_KEY,userAuth.getOnlyAuthenticate());
map.put(OauthConstant.DATA_SECURITY_KEY,userAuth.getDataSecurityVO());
map.put(OauthConstant.COMPANY_NO_KEY,userAuth.getCompanyNo());
map.put(OauthConstant.EXPIRES_IN_KEY,securityConfigProperties.getAccessTokenValiditySeconds());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(map);
}
return accessToken;
};
}
/***
* @description 认证异常处理配置
* @return:
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.authenticationEntryPoint(authenticationEntryPoint())//异常处理
.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()")
.allowFormAuthenticationForClients();
}
/***
* @description 自定义认证异常响应数据
* @return: org.springframework.security.web.AuthenticationEntryPoint
*/
@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return (request, response, e) -> {
response.setStatus(HttpStatus.OK.value());
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Cache-Control", "no-cache");
ResponseResult<Boolean> responseWrap = ResponseResultBuild.build(AuthEnum.NEED_LOGIN, false);
String result = JSONObject.toJSONString(responseWrap);
response.getWriter().print(result);
response.getWriter().flush();
};
}
}
@@ -0,0 +1,19 @@
package com.itheima.sfbx.security.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @ClassName RestTemplateConfig.java
* @Description 远程调用
*/
@Configuration
public class RestTemplateConfig {
//请求客户端
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@@ -0,0 +1,83 @@
package com.itheima.sfbx.security.config;
import com.itheima.sfbx.framework.commons.properties.SecurityConfigProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* @ClassName ReactiveSecurityConfig.java
* @Description 支持web的权限配置
*/
@Configuration
@EnableWebSecurity
@EnableConfigurationProperties(SecurityConfigProperties.class)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
SecurityConfigProperties securityConfigProperties;
@Autowired
UserDetailsService oauth2UserDetailsService;
/**
* BCrypt密码编码
* @return
*/
@Bean
public BCryptPasswordEncoder bcryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 自定义身份认证逻辑
* @return
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//指定用户明细查询及密码处理者
auth.userDetailsService(oauth2UserDetailsService).passwordEncoder(bcryptPasswordEncoder());
super.configure(auth);
}
/**
* 认证管理器
* @return
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
//使用默认的认证管理器
return super.authenticationManagerBean();
}
/***
* @description 过滤器链定义
* @param http
* @return
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()//站点请求处理
.and()
.authorizeRequests()
.antMatchers(securityConfigProperties.getIgnoreUrl()
.toArray(new String[securityConfigProperties.getIgnoreUrl().size()]))//忽略配置
.permitAll()
.anyRequest().authenticated()//其他请求都需要校验
.and()
.csrf().disable();
}
}
@@ -0,0 +1,28 @@
package com.itheima.sfbx.security.details;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import javax.sql.DataSource;
/***
* @description 对JdbcClientDetailsService的增强
*/
public class JdbcClientDetailsServiceImpl extends JdbcClientDetailsService {
//使用数据库中配置进行对象构建
public JdbcClientDetailsServiceImpl(DataSource dataSource) {
super(dataSource);
}
/***
* @description 加载指定客户端配置信息
* @param clientId
* @return
* @return: org.springframework.security.oauth2.provider.ClientDetails
*/
@Override
public ClientDetails loadClientByClientId(String clientId) {
return super.loadClientByClientId(clientId);
}
}
@@ -0,0 +1,85 @@
package com.itheima.sfbx.security.details;
import com.alibaba.fastjson.JSONObject;
import com.itheima.sfbx.framework.commons.constant.basic.SuperConstant;
import com.itheima.sfbx.framework.commons.dto.security.*;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import com.itheima.sfbx.framework.commons.utils.RegisterBeanHandler;
import com.itheima.sfbx.security.feign.CustomerFeign;
import com.itheima.sfbx.security.feign.UserFeign;
import com.itheima.sfbx.security.handler.LoginAuthHandler;
import com.itheima.sfbx.security.base.UserAuth;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @ClassName ReactiveUserDetailsServiceImpl.java
* @Description 支持flum的身份类实现ReactiveUserDetailsService接口
*/
@Slf4j
@Component("oauth2UserDetailsService")
public class Oauth2UserDetailsServiceImpl implements UserDetailsService {
//调用RPC原创服务
@Autowired
UserFeign userFeign;
//调用RPC原创服务
@Autowired
CustomerFeign customerFeign;
@Autowired
RegisterBeanHandler registerBeanHandler;
@Override
public UserDetails loadUserByUsername(String principal) throws UsernameNotFoundException {
UsernameVO usernameVO = JSONObject.parseObject(principal,UsernameVO.class);
//查询用户明细
LoginAuthHandler loginAuthHandler = registerBeanHandler
.getBean(usernameVO.getLoginBeanName(),LoginAuthHandler.class);
UserVO userVO = loginAuthHandler.findUserDetail(usernameVO.getLoginType(),
usernameVO.getUsername(),usernameVO.getCompanyNo());
//UserAuth构建
if (EmptyUtil.isNullOrEmpty(userVO)) {
throw new DisabledException("无效的账号");
}
if (SuperConstant.DATA_STATE_1.equals(userVO.getDataState())) {
throw new LockedException("账户被禁用");
}
//客户端
userVO.setClientId(usernameVO.getClientId());
//资源
List<ResourceVO> resourceVOs = userFeign.findResourceByUserId(userVO.getId());
if (!EmptyUtil.isNullOrEmpty(resourceVOs)){
Set<String> resources = resourceVOs.stream().map(ResourceVO::getRequestPath).collect(Collectors.toSet());
userVO.setResourceRequestPaths(resources);
}
//角色
List<RoleVO> roleVOs = userFeign.findRoleByUserId(userVO.getId());
if (!EmptyUtil.isNullOrEmpty(roleVOs)){
Set<String> roleLabel = roleVOs.stream().map(RoleVO::getLabel).collect(Collectors.toSet());
userVO.setRoleLabels(roleLabel);
}
//数据权限
QueryDataSecurityVO queryDataSecurityVO = QueryDataSecurityVO.builder()
.userId(userVO.getId())
.roleVOs(roleVOs)
.build();
DataSecurityVO dataSecurityVO = userFeign.userDataSecurity(queryDataSecurityVO);
if (!EmptyUtil.isNullOrEmpty(dataSecurityVO)){
userVO.setDataSecurityVO(dataSecurityVO);
}
return new UserAuth(userVO);
}
}
@@ -0,0 +1,26 @@
package com.itheima.sfbx.security.handler;
import com.itheima.sfbx.framework.commons.dto.security.UserVO;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
/**
* @ClassName CacheTokenHandler.java
* @Description 缓存token处理器
*/
public interface CacheTokenHandler {
/***
* @description 缓存token
* @param oAuth2AccessToken
* @return
*/
UserVO cacheToken(OAuth2AccessToken oAuth2AccessToken);
/***
* @description 缓存token
* @param userVO 用户
* @return
*/
Boolean deleteToken(UserVO userVO);
}
@@ -0,0 +1,34 @@
package com.itheima.sfbx.security.handler;
import com.itheima.sfbx.framework.commons.dto.security.CompanyVO;
import com.itheima.sfbx.framework.commons.dto.security.UserVO;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import java.security.Principal;
import java.util.Map;
/**
* @ClassName LoginAdapter.java
* @Description 登陆处理器接口
*/
public interface LoginAuthHandler {
/***
* @description 登录处理
* @param parameters 登录参数
* @return
*/
UserVO loginHandler(Principal principal, Map<String, String> parameters,
String loginBeanName, CompanyVO companyVO)
throws HttpRequestMethodNotSupportedException;
/***
* @description 用户信息查询
* @param loginType 登录类型
* @param username 账号信息:用户名或者手机号或者openId
* @param companyNo 企业号
* @return: com.itheima.easy.vo.security.UserVO
*/
UserVO findUserDetail(String loginType,String username,String companyNo);
}
@@ -0,0 +1,17 @@
package com.itheima.sfbx.security.handler;
import com.itheima.sfbx.framework.commons.dto.security.UserVO;
/**
* @ClassName LogoutHandler.java
* @Description 退出处理器
*/
public interface LogoutHandler {
/***
* @description 退出
* @param userVO 退出用户
* @return
*/
Boolean logout(UserVO userVO);
}
@@ -0,0 +1,19 @@
package com.itheima.sfbx.security.handler;
import com.itheima.sfbx.framework.commons.dto.security.UserVO;
import java.security.Principal;
import java.util.Map;
/**
* @ClassName RefreshTokenHandler.java
* @Description 刷新令牌处理
*/
public interface RefreshTokenHandler {
/***
* @description 刷新令牌
* @return
*/
public UserVO refreshToken(Principal principal, Map<String, String> parameters);
}
@@ -0,0 +1,112 @@
package com.itheima.sfbx.security.handler.impl;
import com.itheima.sfbx.framework.commons.constant.security.OauthCacheConstant;
import com.itheima.sfbx.framework.commons.properties.SecurityConfigProperties;
import com.itheima.sfbx.framework.commons.constant.security.OauthConstant;
import com.itheima.sfbx.security.handler.CacheTokenHandler;
import com.itheima.sfbx.framework.commons.dto.security.DataSecurityVO;
import com.itheima.sfbx.framework.commons.dto.security.UserVO;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.stereotype.Component;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @ClassName CacheTokenHandlerImpl.java
* @Description Token缓存处理
*/
@Component
@EnableConfigurationProperties(SecurityConfigProperties.class)
public class CacheTokenHandlerImpl implements CacheTokenHandler {
@Autowired
RedissonClient redissonClient;
@Autowired
SecurityConfigProperties securityConfigProperties;
@Override
public UserVO cacheToken(OAuth2AccessToken oAuth2AccessToken){
//登录成功后获得oAuth2AccessToken中增强信息
Long id = Long.valueOf(oAuth2AccessToken.getAdditionalInformation().get(OauthConstant.USER_ID_KEY).toString());
String userToken =String.valueOf(oAuth2AccessToken.getAdditionalInformation().get(OauthConstant.JTI_KEY));
String mobile =String.valueOf(oAuth2AccessToken.getAdditionalInformation().get(OauthConstant.MOBILE_KEY));
String openId =String.valueOf(oAuth2AccessToken.getAdditionalInformation().get(OauthConstant.OPEN_ID_KEY));
Set<String> resources = (Set<String>) oAuth2AccessToken.getAdditionalInformation().get(OauthConstant.RESOURCS_KEY);
Set<String> roles = (Set<String>) oAuth2AccessToken.getAdditionalInformation().get(OauthConstant.ROLES_KEY);
String username = String.valueOf(oAuth2AccessToken.getAdditionalInformation().get(OauthConstant.USER_NAME_KEY));
String sex = String.valueOf(oAuth2AccessToken.getAdditionalInformation().get(OauthConstant.SEX_KEY));
String clientId = String.valueOf(oAuth2AccessToken.getAdditionalInformation().get(OauthConstant.CLIENT_ID_KEY));
String realName = String.valueOf(oAuth2AccessToken.getAdditionalInformation().get(OauthConstant.REAL_NAME_KEY));
String deptNo = String.valueOf(oAuth2AccessToken.getAdditionalInformation().get(OauthConstant.DEPT_NO_KEY));
String postNo = String.valueOf(oAuth2AccessToken.getAdditionalInformation().get(OauthConstant.POST_NO_KEY));
Boolean onlyAuthenticate = (Boolean) oAuth2AccessToken.getAdditionalInformation().get(OauthConstant.ONLY_AUTHENTICATE_KEY);
DataSecurityVO dataSecurityVO = (DataSecurityVO) oAuth2AccessToken.getAdditionalInformation().get(OauthConstant.DATA_SECURITY_KEY);
String companyNo = String.valueOf(oAuth2AccessToken.getAdditionalInformation().get(OauthConstant.COMPANY_NO_KEY));
//设置accessToken到redis,获得时使用userToken获取
String accessToken = oAuth2AccessToken.getValue();
RBucket<String> accessTokenBucket = redissonClient.getBucket(OauthCacheConstant.ACCESS_TOKEN + userToken);
long accessTokenExpiresIn = Long.valueOf(oAuth2AccessToken.getAdditionalInformation().get(OauthConstant.EXPIRES_IN_KEY).toString());
accessTokenBucket.set(accessToken,accessTokenExpiresIn, TimeUnit.SECONDS);
//设置refreshToken到redis,获得时使用userToken获取
OAuth2RefreshToken oAuth2RefreshToken = oAuth2AccessToken.getRefreshToken();
String refreshToken = oAuth2RefreshToken.getValue();
long refreshTokenExpiresIn = securityConfigProperties.getRefreshTokenValiditySeconds();
RBucket<String> refreshTokenBucket = redissonClient.getBucket(OauthCacheConstant.REFRESH_TOKEN + userToken);
refreshTokenBucket.set(refreshToken,refreshTokenExpiresIn, TimeUnit.SECONDS);
//绑定username与userToken,用于剔除设置
RBucket<String> usernameBucket = redissonClient.getBucket(OauthCacheConstant.USER_TOKEN_BIND + username);
usernameBucket.set(userToken,refreshTokenExpiresIn, TimeUnit.SECONDS);
//构建返回对象
UserVO userVO = UserVO.builder()
.id(id)
.clientId(clientId)
.companyNo(companyNo)
.username(username)
.userToken(userToken)
.resourceRequestPaths(resources)
.roleLabels(roles)
.openId(openId)
.mobile(mobile)
.deptNo(deptNo)
.postNo(postNo)
.dataSecurityVO(dataSecurityVO)
.onlyAuthenticate(onlyAuthenticate)
.sex(sex)
.realName(realName)
.build();
//设置userVO到redis,获得时候使用userToken获得
RBucket<UserVO> userTokenBucket = redissonClient.getBucket(OauthCacheConstant.USER_TOKEN + userToken);
userTokenBucket.set(userVO,refreshTokenExpiresIn, TimeUnit.SECONDS);
return userVO;
}
@Override
public Boolean deleteToken(UserVO userVO) {
//删除accessToken
RBucket<String> accessTokenBucket = redissonClient.getBucket(OauthCacheConstant.ACCESS_TOKEN + userVO.getUserToken());
boolean deleteAccessToken = accessTokenBucket.delete();
//删除refreshToken
RBucket<String> refreshTokenBucket = redissonClient.getBucket(OauthCacheConstant.REFRESH_TOKEN + userVO.getUserToken());
boolean deleteRefreshToken = refreshTokenBucket.delete();
//删除绑定username
RBucket<String> usernameBucket = redissonClient.getBucket(OauthCacheConstant.USER_TOKEN_BIND + userVO.getUsername());
boolean deleteUsername = usernameBucket.delete();
//删除用户和userToken关联
RBucket<UserVO> userTokenBucket = redissonClient.getBucket(OauthCacheConstant.USER_TOKEN + userVO.getUserToken());
boolean deleteUserTokenBucket = userTokenBucket.delete();
//清理完成
if (deleteAccessToken&&deleteRefreshToken&&deleteUsername&&deleteUserTokenBucket){
return true;
}else {
return false;
}
}
}
@@ -0,0 +1,24 @@
package com.itheima.sfbx.security.handler.impl;
import com.itheima.sfbx.security.handler.CacheTokenHandler;
import com.itheima.sfbx.security.handler.LogoutHandler;
import com.itheima.sfbx.framework.commons.dto.security.UserVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @ClassName LogoutHandlerImpl.java
* @Description 退出处理实现
*/
@Component
public class LogoutHandlerImpl implements LogoutHandler {
@Autowired
CacheTokenHandler cacheTokenHandler;
@Override
public Boolean logout(UserVO userVO) {
return cacheTokenHandler.deleteToken(userVO);
}
}
@@ -0,0 +1,109 @@
package com.itheima.sfbx.security.handler.impl;
import com.alibaba.fastjson.JSONObject;
import com.itheima.sfbx.framework.commons.constant.security.OauthCacheConstant;
import com.itheima.sfbx.framework.commons.constant.security.OauthConstant;
import com.itheima.sfbx.framework.commons.dto.security.CompanyVO;
import com.itheima.sfbx.framework.commons.dto.security.UserVO;
import com.itheima.sfbx.framework.commons.dto.security.UsernameVO;
import com.itheima.sfbx.framework.commons.enums.security.AuthEnum;
import com.itheima.sfbx.framework.commons.exception.ProjectException;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import com.itheima.sfbx.security.handler.CacheTokenHandler;
import com.itheima.sfbx.security.handler.LoginAuthHandler;
import com.itheima.sfbx.security.feign.CustomerFeign;
import com.itheima.sfbx.security.feign.UserFeign;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import java.security.Principal;
import java.util.Map;
/**
* @ClassName MobileLoginAuthHandlerImpl.java
* @Description 手机验证码登录处理器实现
*/
@Slf4j
@Component("mobileLoginAuthHandler")
public class MobileLoginAuthHandlerImpl implements LoginAuthHandler {
@Autowired
TokenEndpoint tokenEndpoint;
@Autowired
CacheTokenHandler cacheTokenHandler;
//调用RPC原创服务
@Autowired
UserFeign userFeign;
//调用RPC原创服务
@Autowired
CustomerFeign customerFeign;
@Autowired
RedissonClient redissonClient;
@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
public UserVO loginHandler(Principal principal, Map<String, String> parameters,
String loginBeanName, CompanyVO companyVO)
throws HttpRequestMethodNotSupportedException {
String mobile = parameters.get(OauthConstant.MOBILE_KEY);
String clientId = parameters.get(OauthConstant.CLIENT_ID_KEY);
String loginType = parameters.get(OauthConstant.LOGIN_TYPE_KEY);
UsernameVO usernameVO = UsernameVO.builder()
.username(mobile)
.clientId(clientId)
.companyNo(companyVO.getCompanyNo())
.loginType(loginType)
.loginBeanName(loginBeanName)
.build();
String username = JSONObject.toJSONString(usernameVO);
parameters.put(OauthConstant.USER_NAME_KEY,username);
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
return cacheTokenHandler.cacheToken(oAuth2AccessToken);
}
@Override
public UserVO findUserDetail(String loginType, String mobile,String companyNo) {
//手机登录处理
String key = OauthCacheConstant.LOGIN_CODE+mobile;
//存储手机发送的验证码到存在中
// RBucket<String> code = redissonClient.getBucket(key);
// if (EmptyUtil.isNullOrEmpty(code.get())){
// throw new ProjectException(AuthEnum.CODE_FAIL);
// }
// String password = bCryptPasswordEncoder.encode(code.get());
String password = bCryptPasswordEncoder.encode("123456");
//处理登录
UserVO userVO = null;
switch (loginType){
case OauthConstant.USER_MOBILE:
userVO = userFeign.mobileLogin(mobile,companyNo);
if (!EmptyUtil.isNullOrEmpty(userVO)){
userVO.setPassword(password);
userVO.setOnlyAuthenticate(false);
}
return userVO;
case OauthConstant.CUSTOMER_MOBILE:
userVO = customerFeign.mobileLogin(mobile,companyNo);
if (!EmptyUtil.isNullOrEmpty(userVO)){
userVO.setPassword(password);
userVO.setOnlyAuthenticate(true);
}
return userVO;
default:
throw new ProjectException(AuthEnum.LOGIN_FAIL);
}
}
}
@@ -0,0 +1,90 @@
package com.itheima.sfbx.security.handler.impl;
import com.itheima.sfbx.framework.commons.constant.security.OauthCacheConstant;
import com.itheima.sfbx.framework.commons.constant.security.OauthConstant;
import com.itheima.sfbx.framework.commons.dto.security.UserVO;
import com.itheima.sfbx.framework.commons.properties.SecurityConfigProperties;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import com.itheima.sfbx.framework.commons.utils.ExceptionsUtil;
import com.itheima.sfbx.security.handler.CacheTokenHandler;
import com.itheima.sfbx.security.handler.RefreshTokenHandler;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName RefreshTokenHandlerImpl.java
* @Description 刷新令牌处理实现
*/
@Slf4j
@Component
public class RefreshTokenHandlerImpl implements RefreshTokenHandler {
@Autowired
RedissonClient redissonClient;
@Autowired
TokenEndpoint tokenEndpoint;
@Autowired
CacheTokenHandler cacheTokenHandler;
@Autowired
UserDetailsService userDetailsService;
@Autowired
SecurityConfigProperties securityConfigProperties;
@Override
public UserVO refreshToken(Principal principal, Map<String, String> parameters) {
Boolean flag = false;
//通过jti获得refreshToken
String userToken = parameters.get(OauthConstant.JTI_KEY);
RBucket<String> refreshTokenBucket = redissonClient.getBucket(OauthCacheConstant.REFRESH_TOKEN + userToken);
String refreshToken = refreshTokenBucket.get();
if (EmptyUtil.isNullOrEmpty(refreshToken)){
return null;
}
//使用refreshToken再次获得OAuth2AccessToken
parameters.put("refresh_token",refreshToken);
OAuth2AccessToken oAuth2AccessToken = null;
try {
oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
} catch (HttpRequestMethodNotSupportedException e) {
log.error("刷新令牌出错:{}", ExceptionsUtil.getErrorMessageWithNestedException(e));
return null;
}
//重新构建jwt增强信息
DefaultOAuth2AccessToken defaultOAuth2AccessToken = (DefaultOAuth2AccessToken) oAuth2AccessToken;
RBucket<UserVO> jtiUserBucket = redissonClient.getBucket(OauthCacheConstant.USER_TOKEN + userToken);
UserVO userVO = jtiUserBucket.get();
Map<String, Object> map = new HashMap<>(11);
map.put(OauthConstant.USER_ID_KEY, userVO.getId());
map.put(OauthConstant.CLIENT_ID_KEY, userVO.getClientId());
map.put(OauthConstant.USER_NAME_KEY,userVO.getUsername());
map.put(OauthConstant.RESOURCS_KEY,userVO.getResourceRequestPaths());
map.put(OauthConstant.ROLES_KEY,userVO.getRoleLabels());
map.put(OauthConstant.OPEN_ID_KEY,userVO.getOpenId());
map.put(OauthConstant.DEPT_NO_KEY,userVO.getDeptNo());
map.put(OauthConstant.POST_NO_KEY,userVO.getPostNo());
map.put(OauthConstant.MOBILE_KEY,userVO.getMobile());
map.put(OauthConstant.COMPANY_NO_KEY,userVO.getCompanyNo());
map.put(OauthConstant.DATA_SECURITY_KEY,userVO.getDataSecurityVO());
//令牌自定过期时间
map.put(OauthConstant.EXPIRES_IN_KEY,securityConfigProperties.getAccessTokenValiditySeconds());
defaultOAuth2AccessToken.setAdditionalInformation(map);
//处理缓存信息
return cacheTokenHandler.cacheToken(defaultOAuth2AccessToken);
}
}
@@ -0,0 +1,88 @@
package com.itheima.sfbx.security.handler.impl;
import com.alibaba.fastjson.JSONObject;
import com.itheima.sfbx.framework.commons.constant.security.OauthConstant;
import com.itheima.sfbx.framework.commons.dto.security.CompanyVO;
import com.itheima.sfbx.framework.commons.enums.security.AuthEnum;
import com.itheima.sfbx.framework.commons.exception.ProjectException;
import com.itheima.sfbx.security.handler.CacheTokenHandler;
import com.itheima.sfbx.security.handler.LoginAuthHandler;
import com.itheima.sfbx.security.feign.CustomerFeign;
import com.itheima.sfbx.security.feign.UserFeign;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import com.itheima.sfbx.framework.commons.dto.security.UserVO;
import com.itheima.sfbx.framework.commons.dto.security.UsernameVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import java.security.Principal;
import java.util.Map;
/**
* @ClassName UserpasswordLoginAdapter.java
* @Description 密码登录处理器实现
*/
@Slf4j
@Component("usernameLoginAuthHandler")
public class UsernameLoginAuthHandlerImpl implements LoginAuthHandler {
@Autowired
TokenEndpoint tokenEndpoint;
@Autowired
CacheTokenHandler cacheTokenHandler;
//调用RPC原创服务
@Autowired
UserFeign userFeign;
//调用RPC原创服务
@Autowired
CustomerFeign customerFeign;
@Override
public UserVO loginHandler(Principal principal, Map<String, String> parameters,
String loginBeanName, CompanyVO companyVO)
throws HttpRequestMethodNotSupportedException {
String username = parameters.get(OauthConstant.USER_NAME_KEY);
String clientId = parameters.get(OauthConstant.CLIENT_ID_KEY);
String loginType = parameters.get(OauthConstant.LOGIN_TYPE_KEY);
UsernameVO usernameVO = UsernameVO.builder()
.username(username)
.companyNo(companyVO.getCompanyNo())
.clientId(clientId)
.loginType(loginType)
.loginBeanName(loginBeanName)
.build();
username = JSONObject.toJSONString(usernameVO);
parameters.put(OauthConstant.USER_NAME_KEY,username);
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
return cacheTokenHandler.cacheToken(oAuth2AccessToken);
}
@Override
public UserVO findUserDetail(String loginType, String username,String companyNo) {
UserVO userVO = null;
//处理登录
switch (loginType){
case OauthConstant.USER_USERNAME:
userVO = userFeign.usernameLogin(username,companyNo);
if (!EmptyUtil.isNullOrEmpty(userVO)){
userVO.setOnlyAuthenticate(false);
}
return userVO;
case OauthConstant.CUSTOMER_USERNAME:
userVO = customerFeign.usernameLogin(username,companyNo);
if (!EmptyUtil.isNullOrEmpty(userVO)){
userVO.setOnlyAuthenticate(true);
}
return userVO;
default:
throw new ProjectException(AuthEnum.LOGIN_FAIL);
}
}
}
@@ -0,0 +1,111 @@
package com.itheima.sfbx.security.handler.impl;
import com.alibaba.fastjson.JSONObject;
import com.itheima.sfbx.framework.commons.constant.security.CompanyConstant;
import com.itheima.sfbx.framework.commons.constant.security.OauthConstant;
import com.itheima.sfbx.framework.commons.dto.security.*;
import com.itheima.sfbx.framework.commons.enums.security.AuthEnum;
import com.itheima.sfbx.framework.commons.exception.ProjectException;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import com.itheima.sfbx.security.feign.CustomerFeign;
import com.itheima.sfbx.security.feign.UserFeign;
import com.itheima.sfbx.security.handler.CacheTokenHandler;
import com.itheima.sfbx.security.handler.LoginAuthHandler;
import com.itheima.sfbx.security.wechat.WechatService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import java.security.Principal;
import java.util.Map;
/**
* @ClassName WechatLoginAuthHandlerImpl.java
* @Description 微信登录处理
*/
@Component("wechatLoginAuthHandler")
public class WechatLoginAuthHandlerImpl implements LoginAuthHandler {
//调用RPC原创服务
@Autowired
UserFeign userFeign;
//调用RPC原创服务
@Autowired
CustomerFeign customerFeign;
@Autowired
TokenEndpoint tokenEndpoint;
@Autowired
CacheTokenHandler cacheTokenHandler;
@Autowired
WechatService wechatService;
@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
public UserVO loginHandler(Principal principal, Map<String, String> parameters,
String loginBeanName, CompanyVO companyVO)
throws HttpRequestMethodNotSupportedException {
String code = parameters.get(OauthConstant.CODE_KEY);
String clientId = parameters.get(OauthConstant.CLIENT_ID_KEY);
String loginType = parameters.get(OauthConstant.LOGIN_TYPE_KEY);
//code兑换openId
AuthChannelVO authChannelVO = companyVO.getAuthChannelVOs().stream().filter(n -> {
return n.getChannelLabel().equals(CompanyConstant.CHANNEL_LABEL_WECHAT);
}).findFirst().get();
//微信拿openId
String openId = wechatService.openId(authChannelVO.getAppId(), authChannelVO.getAppSecret(), code);
UsernameVO usernameVO = UsernameVO.builder()
.username(openId)
.clientId(clientId)
.companyNo(companyVO.getCompanyNo())
.loginType(loginType)
.loginBeanName(loginBeanName)
.build();
String username = JSONObject.toJSONString(usernameVO);
parameters.put(OauthConstant.USER_NAME_KEY,username);
parameters.put(OauthConstant.PASSWORD_KEY,openId);
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
return cacheTokenHandler.cacheToken(oAuth2AccessToken);
}
@Override
public UserVO findUserDetail(String loginType, String openId,String companyNo) {
//处理登录
UserVO userVO = null;
String password = bCryptPasswordEncoder.encode(openId);
switch (loginType){
case OauthConstant.USER_WECHAT:
userVO = userFeign.wechatLogin(openId,companyNo);
if (!EmptyUtil.isNullOrEmpty(userVO)){
userVO.setPassword(password);
userVO.setOnlyAuthenticate(false);
}
return userVO;
case OauthConstant.CUSTOMER_WECHAT:
userVO = customerFeign.wechatLogin(openId,companyNo);
//如果用户不存在则注册用户
if (EmptyUtil.isNullOrEmpty(userVO)){
CustomerVO customerVO = CustomerVO.builder()
.username(openId)
.openId(openId)
.password(password)
.build();
return customerFeign.registerUser(customerVO);
}
userVO.setPassword(password);
userVO.setOnlyAuthenticate(true);
return userVO;
default:
throw new ProjectException(AuthEnum.LOGIN_FAIL);
}
}
}
@@ -0,0 +1,101 @@
package com.itheima.sfbx.security.web;
import com.itheima.sfbx.framework.commons.basic.ResponseResult;
import com.itheima.sfbx.framework.commons.constant.basic.SuperConstant;
import com.itheima.sfbx.framework.commons.constant.security.OauthConstant;
import com.itheima.sfbx.framework.commons.dto.security.UserVO;
import com.itheima.sfbx.framework.commons.enums.basic.BaseEnum;
import com.itheima.sfbx.framework.commons.utils.ResponseResultBuild;
import com.itheima.sfbx.framework.commons.utils.SubjectContent;
import com.itheima.sfbx.security.adepter.LoginAuthAdepter;
import com.itheima.sfbx.security.handler.LogoutHandler;
import com.itheima.sfbx.security.handler.RefreshTokenHandler;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;
import java.security.Principal;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 自定义Oauth2获取令牌接口
*/
@RestController
@RequestMapping("/oauth")
@Api(tags = "登录认证")
public class AuthController {
@Autowired
private LoginAuthAdepter loginAuthAdepter;
@Autowired
private LogoutHandler logoutHandler;
@Autowired
private RefreshTokenHandler refreshTokenHandler;
/**
* Oauth2登录认证
* 登录必传:
* client_id:客户端
* client_secret:客户端秘钥
* grant_type:登录传入:password
* login_typeOauthConstant中选择:USER_USERNAME、USER_MOBILE、CUSTOMER_USERNAME、CUSTOMER_MOBILE
* username:用户账号【用户名、手机号】
* password:用户密码【明文密码、手机验证码】
* code:三方授权登录传入的授权码
* 刷新必传:
* client_id:客户端
* grant_type:refresh_token
* client_secret:客户端秘钥
* jti:本次回话标识
*/
@ApiOperation(value = "登录认证",notes = "登录认证")
@ApiImplicitParams({
@ApiImplicitParam(name = "grant_type", value = "授权模式", example = "password",required = true),
@ApiImplicitParam(name = "client_id", value = "Oauth2客户端ID",example = "operators-pc", required = true),
@ApiImplicitParam(name = "client_secret",value = "Oauth2客户端秘钥", example = "pass", required = true),
@ApiImplicitParam(name = "username", value = "登录用户名",example = "admin@qq.com"),
@ApiImplicitParam(name = "mobile", value = "登录用户名", example = "15156403088"),
@ApiImplicitParam(name = "usetToken",value = "会话usetToken",required = true),
@ApiImplicitParam(name = "password", value = "登录密码", example = "pass"),
@ApiImplicitParam(name = "login_type", value = "登录类型", example = "详解OauthConstant登录类型"),
@ApiImplicitParam(name = "code", value = "小程序code")
})
@RequestMapping(value = "/token", method = RequestMethod.POST)
public ResponseResult<UserVO> postAccessToken(@ApiIgnore Principal principal,
@ApiIgnore @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
UserVO userVO = null;
boolean isRefreshToken = OauthConstant.GRANT_TYPE_REFRESH_TOKEN
.equals(parameters.get(OauthConstant.GRANT_TYPE_KEY));
//令牌刷新
if (isRefreshToken){
userVO =refreshTokenHandler.refreshToken(principal, parameters);
//用户登录
}else {
userVO = loginAuthAdepter.adepterRoutes(principal, parameters);
}
return ResponseResultBuild.build(BaseEnum.SUCCEED, userVO);
}
/***
* @description 退出接口
* @return
*/
@ApiOperation(value = "退出登录",notes = "退出登录")
@RequestMapping(value = "/logout", method = RequestMethod.POST)
public ResponseResult<Boolean> logout() {
UserVO userVO = SubjectContent.getUserVO();
Boolean flag = logoutHandler.logout(userVO);
return ResponseResultBuild.build(BaseEnum.SUCCEED, flag);
}
}
@@ -0,0 +1,37 @@
package com.itheima.sfbx.security.web;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.KeyPair;
import java.security.interfaces.RSAPublicKey;
import java.util.Map;
@Slf4j
@Api(tags = "获取公钥")
@RestController
@RequestMapping("rsa")
public class PublicKeyController {
@Autowired
private KeyPair keyPair;
@ApiOperation(value = "获取公钥",notes = "获取公钥")
@GetMapping("/public-key")
public Map<String, Object> loadPublicKey() {
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAKey key = new RSAKey.Builder(publicKey).build();
Map<String, Object> map = new JWKSet(key).toJSONObject();
log.info("========获得公钥的信息:{}===========",map.toString());
return map;
}
}
@@ -0,0 +1,19 @@
package com.itheima.sfbx.security.wechat;
/**
* @ClassName wechatServer.java
* @Description 微信接口服务
*/
public interface WechatService {
/***
* @description 查询用户openId
* @param appId 应用
* @param appSecret
* @param code
* @return
*/
String openId(String appId,String appSecret,String code);
}
@@ -0,0 +1,29 @@
package com.itheima.sfbx.security.wechat.impl;
import com.alibaba.fastjson.JSONObject;
import com.itheima.sfbx.security.wechat.WechatService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
/**
* @ClassName WechatServiceImpl.java
* @Description TODO
*/
@Service
public class WechatServiceImpl implements WechatService {
@Autowired
RestTemplate restTemplate;
@Override
public String openId(String appId, String appSecret, String code) {
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+appId+
"&secret="+appSecret+
"&code="+code+
"&grant_type=authorization_code" ;
String wechatString = restTemplate.getForObject(url, String.class);
JSONObject jsonObject = JSONObject.parseObject(wechatString);
return jsonObject.getString("openid");
}
}
@@ -0,0 +1,10 @@
_ __ __
(_) /__________ ______/ /_
/ / __/ ___/ __ `/ ___/ __/
/ / /_/ /__/ /_/ (__ ) /_
/_/\__/\___/\__,_/____/\__/
:: Spring Boot :: (v-2.7.10)
:: Spring Cloud :: (v-2021.0.6)
:: Spring Cloud Alibaba :: (v-2021.0.1.0)
:: Itheima Project Cloud :: (V-1.0.2-SNAPSHOT)
:: 献给可爱的传智人 ::
@@ -0,0 +1,45 @@
#服务配置
server:
#端口
port: 7078
#服务编码
tomcat:
uri-encoding: UTF-8
spring:
config:
activate:
on-profile:
- test
main:
allow-circular-references: true
allow-bean-definition-overriding: true
mvc:
pathmatch:
matching-strategy: ant_path_matcher
#应用配置
application:
#应用名称
name: security-oauth
cloud:
nacos:
discovery:
server-addr: ${NACOS_ADDRESS:nacos-service.yjy-public-sfbx-java.svc.cluster.local:20015} # nacos注册中心
group: SEATA_GROUP
service: ${spring.application.name}
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:PKsf*bxQ4;yP3a+}
config:
server-addr: ${NACOS_ADDRESS:nacos-service.yjy-public-sfbx-java.svc.cluster.local:20015} # nacos注册中心
group: SEATA_GROUP
file-extension: yml
shared-configs: # 共享配置
- data-id: shared-spring-task.yml #配置文件名-DataId
group: SEATA_GROUP
refresh: false
- data-id: shared-redisson.yml #配置文件名-DataId
group: SEATA_GROUP
refresh: false
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:PKsf*bxQ4;yP3a+}
logging:
config: classpath:logback.xml
@@ -0,0 +1,39 @@
#服务配置
server:
#端口
port: 7078
#服务编码
tomcat:
uri-encoding: UTF-8
spring:
profiles:
active: dev
main:
allow-circular-references: true
allow-bean-definition-overriding: true
mvc:
pathmatch:
matching-strategy: ant_path_matcher
#应用配置
application:
#应用名称
name: security-oauth
cloud:
nacos:
discovery:
server-addr: 192.168.12.129:8848 # nacos注册中心
group: SEATA_GROUP
service: ${spring.application.name}
config:
server-addr: 192.168.12.129:8848 # nacos配置中心地址
group: SEATA_GROUP
file-extension: yml
shared-configs: # 共享配置
- data-id: shared-spring-task.yml #配置文件名-DataId
group: SEATA_GROUP
refresh: false
- data-id: shared-redisson.yml #配置文件名-DataId
group: SEATA_GROUP
refresh: false
logging:
config: classpath:logback.xml
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径 -->
<property name="LOG_HOME" value="/data/logs/security-oauth" />
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名 -->
<FileNamePattern>${LOG_HOME}/security-oauth.log.%d{yyyy-MM-dd}.log
</FileNamePattern>
<!--日志文件保留天数 -->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern>
</encoder>
<!--日志文件最大的大小 -->
<triggeringPolicy
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- show parameters for hibernate sql 专为 Hibernate 定制 -->
<logger name="org.hibernate.type.descriptor.sql.BasicBinder"
level="TRACE" />
<logger name="org.hibernate.type.descriptor.sql.BasicExtractor"
level="DEBUG" />
<logger name="org.hibernate.SQL" level="DEBUG" />
<logger name="org.hibernate.engine.QueryParameters" level="DEBUG" />
<logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG" />
<!--myibatis log configure -->
<logger name="com.apache.ibatis" level="TRACE" />
<logger name="java.sql.Connection" level="DEBUG" />
<logger name="java.sql.Statement" level="DEBUG" />
<logger name="java.sql.PreparedStatement" level="DEBUG" />
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
<!--日志异步到数据库 -->
</configuration>