spring-security认识及项目应用-202509120153
spring-security认识及项目应用
spring-security认识及项目应用
官方说明:https://docs.spring.io/spring-security/reference/index.html
spring-security介绍
什么是spring-security
Spring Security 是一个为 Spring 应用提供认证和权限控制的安全框架,功能强大、可扩展、可配
置。
核心功能
Spring Security 与其他安全方案对比
功能
说明
认证(Authentication)
验证用户是谁(比如登录)
授权(Authorization)
判断用户是否有权限访问资源(比如访问某个
接口或操作)
基于角色的访问控制
控制不同角色用户的访问权限,如 ADMIN、
USER
密码加密与管理
支持多种加密算法,如 BCrypt,保障账号安
全
防护功能
防止常见攻击:CSRF、XSS、Session Fixatio
n、Clickjacking 等
集成第三方认证
支持 OAuth2、JWT、LDAP、CAS、SSO 等
方法级安全控制
通过注解如 @PreAuthorize 、@Secure
d 实现细粒度控制
URL 访问控制
可以配置哪些路径谁可以访问,如 /admin/
** 只能管理员访问
自动登录/记住我
提供“记住我”功能,在 Cookie 中保存登录状
态
集成spring-security
对比维度
Spring Security
Apache Shiro
手动拦截器/Filter
✅ 功能丰富度
功能非常全,支持认
证、授权、加密、防
攻击、OAuth2、JW
T 等
功能偏轻,适合权限
认证,但不支持 OAu
th2 等高级特性
需要自己实现所有功
能,灵活但容易出错
✅ 与 Spring 集成
原生支持,Spring Bo
ot 自动配置,生态丰
富
需要手动整合 Spring
自己维护拦截器链或
过滤器,整合 Spring
较繁琐
✅ 灵活性
高,自定义 Provide
r、过滤器链、表达式
权限、注解控制
中等,认证逻辑可自
定义,权限控制有限
非常高,但不规范,
权限控制常零散在各
处
✅ 安全性
默认安全性高(CSR
F、Session Fixatio
n、密码加密等开箱
即用)
需手动配置安全防护
容易遗漏关键安全点
(如 CSRF、XSS),
不利于合规
✅ 社区支持
Spring 官方支持,文
档丰富,社区活跃
社区相对小,维护活
跃度一般
无框架支持,社区支
持取决于你用的技术
栈
❌ 学习成本
学习曲线较陡,配置
复杂,概念多(Filter
Chain、Provider、To
ken等)
学习门槛低,上手快
无学习成本,但长期
维护代价高
❌ 配置复杂度
DSL 配置语法灵活但
容易误用,注解、配
置项较多
配置直观,代码集中
自由配置,但容易混
乱、不统一
❌ 适用范围
中大型 Spring 项目、
微服务、需要 OAuth
2、JWT 的系统
小型、权限简单的系
统
快速开发、个人小项
目、临时性系统
如果引入spring-boot-starter-security 依赖,恭喜已经有了基本的登录功能了,其实还没
有如何的业务代码
但是在接口访问的的时候检查cookie,如果没有会转跳到默认的登录界面
要问为什么,只能说这是spring-securtiy的默认功能(不喜欢就配置自己的登录)
登录
登录校验
实现验证请求中用户信息
配置使用的验证方式
/**
*创建认证管理器,使用 DaoAuthenticationProvider 进行基于数据库的认证。
*/
@Bean
public AuthenticationManager
authenticationManager(UserDetailsService userDetailsService) {
DaoAuthenticationProvider provider = new
DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return new ProviderManager(provider);
}
/**
*/
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new
DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService());
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
/**
UserDetailsServiceImpl
数据库查询数据用于上面的DaoAuthenticationProvider,提供的用户数据
只要将用户数据查出来组装成UserDetails即可,具体的校验校验逻辑由spring-security实现(比如密
码的校验)
做完这些我们就得到了基本的用户登录校验,如果用户没有登录,那么就会被拦截
只用请求中携带的信息与查询到的用户名密码匹配才能正确访问
*注入自定义的用户详情服务实现类。
*/
@Bean
public UserDetailsService userDetailsService() {
return new UserDetailsServiceImpl(); // 实现见下
}
/**
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
SysUserPo sysUser = userMapper.selectOne(new
LambdaQueryWrapper
if (sysUser == null) {
throw new UsernameNotFoundException("用户不存在");
}
return User.builder()
.username(sysUser.getUsername())
.password(sysUser.getPassword())
.disabled(false)
.roles("USER") // 角色也可以从数据库读取
.build();
}
}
验证的基本流程
如何获取登录信息
jwt是无状态登录,服务器不存储登录信息
sessin是有状态登录,服务器是存储sessin的,所以并不需要再次查询数据库验证
Provider与ProviderManager
在 Spring Security 中,AuthenticationProvider 是认证的核心接口之一,用于处理具体的认证逻
辑。 DaoAuthenticationProvider 就是它的一个实现类,用户数据库查询验证
ProviderManager 它负责管理多个 AuthenticationProvider,根据当前请求的 Authentication 类型
选择合适的 provider 来完成认证。
1.调用 AuthenticationManager.authenticate(authentication)
↓
2. 遍历所有 provider,找到支持当前 authentication 类型的 provider →
DaoAuthenticationProvider
↓
3. DaoAuthenticationProvider.authenticate() 开始执行:
a. 使用 UserDetailsService.loadUserByUsername(username) 加载用户详情
b. 检查用户是否存在
c. 使用 PasswordEncoder.matches(rawPassword, encodedPassword) 验证密码
d. 如果都通过,构建已认证的 UsernamePasswordAuthenticationToken 并返回
↓
4. 认证成功,
SecurityContextHolder.getContext().setAuthentication(authentication)
↓
5. 后续权限控制基于这个 authentication 对象
请求到达
↓
经过 Security 过滤器链
↓
→ 如果是 Session 模式:
→ 如果是 JWT 模式:
如何选用合适的provider?
自定义的多个provider会有各自的supports() 方法,表示支持哪种Authentication,只有支持的
才会走对应的provider
而DaoAuthenticationProvider 默认支持的是UsernamePasswordAuthenticationT
oken
seeeion登录
根据响应头j-session设置cookie是浏览器的默认行为,当然后续携带也是浏览器的行为(前端无需
操作)
所以我们只要验证后设置session即可
@Override
public boolean supports(Class authentication) {
return
SmsAuthenticationToken.class.isAssignableFrom(authentication);
}
/**
*前端再cookie中设置jsessionId,然后每次请求都带上sessionId
*
security的功能)
*/
@PostMapping("/login-session")
public Response
loginRequest, HttpServletRequest request) {
UsernamePasswordAuthenticationToken token =
new
UsernamePasswordAuthenticationToken(loginRequest.getUsername(),
loginRequest.getPassword());
try {
// 调用 Spring Security 的认证机制(走
DaoAuthenticationProvider)
Authentication authentication =
authenticationManager.authenticate(token);
// ✅ 设置认证信息到 SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);
jwt登录
什么是jwt
JWT 是一种 基于 JSON 格式 的令牌,具有以下特点:
无状态:服务端不保存登录会话,全部信息都在 token 中。
自包含:token 本身包含了用户身份、权限等信息。
可验证性强:token 是用私钥签名的,服务端可以验证其完整性和真实性。
一个典型的 JWT 看起来像这样(其实是base64字符):
它由三部分组成(中间用 . 分隔):
代码
登录,输入用户名,密码,返回jwt
// ✅ 将认证信息写入 HttpSession(注意:Spring Security 会自动使
用 SESSION_ID 做关联)
HttpSession session = request.getSession(true); // true 表示
没有就创建
session.setAttribute("SPRING_SECURITY_CONTEXT",
SecurityContextHolder.getContext());
return Response.success("登录成功,sessionId: " +
session.getId());
} catch (BadCredentialsException e) {
return Response.error("用户名或密码错误");
}
}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6ImpvaG4iLCJpYXQiOjE1MTYyMzkwMjJ9.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
部分
作用
内容示例
Header
说明使用的算法
{"alg": "HS256", "ty
p": "JWT"}
Payload
存放数据(如用户ID、角色
等)
{"sub": "123456789
0", "name": "john",
"iat": 1516239022}
Signature
签名(防篡改)
对前两部分使用密钥签名
返回jwt字符串即可,剩下的让前端来(前端在收到token后存储,后续每次请求携带token)
生成与解析jwt的工具类
@PostMapping("/login-jwt")
public Response
loginRequest) {
UsernamePasswordAuthenticationToken token =
new
UsernamePasswordAuthenticationToken(loginRequest.getUsername(),
loginRequest.getPassword());
try {
Authentication authentication =
authenticationManager.authenticate(token);
// 登录成功,生成 JWT
String username = authentication.getName();
Map
"roles", authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.toList()
);
String jwtToken = JwtUtil.generateToken(username, claims);
return Response.success(jwtToken);
} catch (BadCredentialsException e) {
return Response.error("用户名或密码错误");
}
}
/**
*
*/
public class JwtUtil {
// ✅ 推荐保存在配置中(application.yml),这里写死方便演示
private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor("my-
super-secret-key-my-super-secret-key".getBytes());
private static final long EXPIRATION_MS = 1000 * 60 * 60 * 24; // 1
天
jwt过滤器
拦截请求,将请求中的Authorization请求头获取到(我们返回的token),然后用token解析后的userna
me作为依据,查询数据库即可
直接用userName和role生成已经认证的UsernamePasswordAuthenticationToken,new方法默认
已经设置成了已经认证了(role中包含了角色和权限了,当然)
后面放到了SecurityContextHolder,整个认证流程结束
/**
*/
public static String generateToken(String username, Map
Object> claims) {
return Jwts.builder()
.setSubject(username)
.addClaims(claims)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() +
EXPIRATION_MS))
.signWith(SECRET_KEY, SignatureAlgorithm.HS256)
.compact();
}
public static Claims parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token)
.getBody();
}
}
/**
*
*/
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final Set
//"/auth/**",
"/public/**"
);
private final AntPathMatcher matcher = new AntPathMatcher();
/**
*
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0b20iLCJyb2xlcyI6WyJST0xFX1VTRVIiXSwiaW
F0IjoxNzUyMDUxODEyLCJleHAiOjE3NTIxMzgyMTJ9.HHQSqrs8_HZs2_F-
5FpsNRJBwVOcprxtSQ7H3VimZDg
*/
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String path = request.getRequestURI();
// 放行 OPTIONS 请求
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return;
}
//如果token错误也能放行
if (shouldSkip(path)){
filterChain.doFilter(request, response);
return; // 一定要加return,防止后续代码执行
}
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
try {
Claims claims =
JwtUtil.parseToken(token.replace("Bearer ", ""));
String username = claims.getSubject();
List
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(
问题:token中也没有密码如何防伪
jwt-token的签名是我们经过密钥加密得出的,如果是篡改,自然会解析token失败
Github-oauth2.0登录(授权码模式)
授权码模式-适用于与三方对接的登录,如微信扫码登录
登录步骤
前端逻辑
username,
null,
roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toLi
st())
);
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-
8");
response.getWriter().write("{\"message\":\"Token无效或已
过期\"}");
return;
}
}
filterChain.doFilter(request, response);
}
private boolean shouldSkip(String path) {
return SKIP_PATHS.stream().anyMatch(p -> matcher.match(p,
path));
}
}
用户授权:跳转到 GitHub 授权页面。
用户同意授权:GitHub 返回授权码code。
获取 Access Token:使用授权码向 GitHub 请求 Access Token。
使用 Access Token 获取用户信息:通过 Access Token 获取 GitHub 用户的详细信息。
绑定或创建本地账户:根据 GitHub 用户信息查询并绑定本地账户,或者提示用户注册。
点击按钮跳转到github认证界面
github会拼上我们的回调地址,认证完成后浏览器会重定向到我们的回调界面(当然是我们的前端地
址)
从重定向的url中拿到code请求后端
点击下方按钮,使用 GitHub 账号登录 登录后将自动跳转回系统GitHub 登录
const CLIENT_ID = 'Ov23li8MMBlYvaWv1Du6';
// 回调地址不带state参数
const REDIRECT_URI = encodeURIComponent('http://localhost:3000/github-
callback'); // 回调到前端页面
const STATE = 'login';
//OAuth 推荐在授权请求中带上 state 参数,GitHub 授权后会原样带回回调地址(oauth标
准参数)
function loginWithGithub() {
const githubAuthUrl = `https://github.com/login/oauth/authorize?
client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&scope=read:user&sta
te=${STATE}`;
window.location.href = githubAuthUrl;
}
{
// GitHub 登录
const res = await
fetch(`http://localhost:8080/auth/github/callback?
code=${encodeURIComponent(code)}&state=${encodeURIComponent(state)}`, {
method: 'GET'
})
后端逻辑
后端拿到前端请求github活动的的code,通过code请求获取accessToken,再通过accessToken获取
用户信息,根据返回的github的用户信息判断是否在我们系统中有绑定的用户
如果有,则登录成功(验证过程在github进行,拿到的code也是有时效性的,secret也是只有后端会存
储,当然是选择相信)
if (!res.ok) throw new Error('网络错误')
const data = await res.json()
public Response loginFromCode(String code){
//// 1. 用前端专挑github认证的code 换 access_token
String accessToken = getAccessToken(code);
//// 2. 获取 GitHub 用户信息
JSONObject githubUser = getGithubUser(accessToken); // 包含
login, id, email 等
System.out.println("GitHub 用户信息: " + githubUser);
//
// 3. 查询是否已绑定本地账号
UserThirdPartyPo thirdPartyPo = userThirdPartyService.getOne(
认证通过返回jwt即登录成功
权限控制(Authorization)
在 Spring Security 中,权限控制的核心是基于 用户身份 与 角色或权限 之间的关系来判断用户是
否有访问某个资源的权限。
关键概念:
Spring Security 权限模型
Spring Security 的权限模型通常基于以下几种方式:
1. 基于角色(Role-Based Access Control, RBAC)
new QueryWrapper
ThirdUserProviderEnum.GITHUB.getCode())
.eq("third_party_id", githubUser.getString("id")));
if (thirdPartyPo == null){
return Response.error("未绑定本地账号,请先注册账号并绑定");
}
long userId = thirdPartyPo.getUserId();
SysUserPo user = userService.getById(userId);
if (user == null){
return Response.error("未找到用户");
}
//返回token
String jwt = JwtUtil.generateToken(user.getUsername(),
Map.of("roles", "ROLE_USER"));
return Response.success(jwt);
概念
说明
Principal
当前登录用户对象,通常是 UserDetail
s 的实例
Authentication
认证信息,包括用户名、密码、权限等
GrantedAuthority
授予当前用户的权限,通常是字符串,如 RO
LE_ADMIN 、user:read 等
SecurityContext
存储当前用户的 Authentication ,全局
共享
AccessDecisionManager
权限决策器,用来判断当前用户是否有权限访
问目标资源
用户被分配若干“角色”,如 ROLE_ADMIN 、ROLE_USER ,资源则限制只有特定角色用户可
访问。
2. 基于权限(Permission-Based Access Control)
角色进一步细分为具体操作权限,如 user:add 、user:delete ,更细粒度。
3.基于资源路径的权限控制
通过 URL 拦截规则限制资源访问权限
权限控制流程(简要)
1. 用户登录后,UserDetailsService 加载用户信息与权限。
2. 认证成功后,Authentication 存入 SecurityContextHolder 。
3. 每次访问接口,Spring Security 的 FilterChain 会拦截请求,提取 Authenticatio
n 。
4. 根据访问路径与注解等,调用 AccessDecisionManager 判断是否允许访问。
常用权限控制方式
1. 方法级控制(推荐)
使用注解控制权限:
@PreAuthorize : 在方法调用前进行权限判断。
@Secured : 类似,但语法更简单,只支持 ROLE_ 开头。
@PostAuthorize : 在方法执行后再判断。
⚠️ 使用 @PreAuthorize 需要启用:@EnableGlobalMethodSecurity(prePostEna
bled = true)
2. 基于 URL 的权限配置
在配置类中自定义拦截规则:
@PreAuthorize("hasRole('ADMIN')")
public void adminOnly() {}
@PreAuthorize("hasAuthority('user:delete')")
public void deleteUser() {}
@Secured("ROLE_USER")
public void normalUser() {}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws
Exception {
权限数据存储
通常在数据库中存储用户、角色、权限三张表:
user (用户表)
role (角色表)
permission (权限表)
通过多对多关系建立权限控制(使用中间表关联)。
案例
登录并设置登录人权限(其实就是再前面返回UserDetails的基础上,将权限信息添加到其中返回)
http
.csrf(csrf -> csrf.disable()) // 关闭CSRF(前后端分离建议关闭)
.cors(Customizer.withDefaults())
.authorizeHttpRequests(auth -> auth
// 设置接口的角色权限
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest()
.authenticated()
)
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
SysUserPo sysUser =
userMapper.selectOne(new LambdaQueryWrapper
().eq(SysUserPo::getUsername, username));
if (sysUser == null) {
throw new UsernameNotFoundException("用户不存在");
}
// 查询角色
List
userRoleService.list(new LambdaQueryWrapper
().eq(UserRolePo::getUserId, sysUser.getId()));
if (userRoles.isEmpty()) {
log.info("用户没有角色");
// 即便没有角色也要返回用户,权限为空
return new
org.springframework.security.core.userdetails.User(sysUser.getUsername(
), sysUser.getPassword(),
Collections.emptyList());
}
// 查询角色名称列表
List
roleService.listByIds(userRoles.stream().map(UserRolePo::getRoleId).toL
ist())
.stream()
.map(RolePo::getCode)
.toList();
if (roleCodes.isEmpty()) {
log.info("没有查询到对应角色");
// 即便没有角色也要返回用户,权限为空
return new
org.springframework.security.core.userdetails.User(sysUser.getUsername(
), sysUser.getPassword(),
Collections.emptyList());
}
// 先构造一个包含角色的 userDetails
List
roleCodes.stream().map(role -> new
SimpleGrantedAuthority("ROLE_" + role)).toList();
UserDetails tempUser =
new
org.springframework.security.core.userdetails.User(sysUser.getUsername(
), sysUser.getPassword(),
roleAuthorities);
// 调用 getAuthorityUser 添加权限
return getAuthorityUser(tempUser);
}
/**
*/
public UserDetails getAuthorityUser(UserDetails user) {
// 获取角色名(如 USER, ADMIN)
List
.stream()
.map(GrantedAuthority::getAuthority)
.filter(authority -> authority.startsWith("ROLE_"))
.map(authority -> authority.substring(5))
.toList();
List
LambdaQueryWrapper
if (rolePos.isEmpty()) {
return user;
deliver的请求jwt校验
安全过滤器链-的配置类-SecurityFilterChain
}
List
= rolePos.stream().map(RolePo::getId).toList();
// 查角色对应的权限
List
rolePermissionService.list(
new LambdaQueryWrapper
().in(RolePermissionPo::getRoleId, roleIds));
if (rolePermissionList.isEmpty()) {
return user;
}
// 批量查权限
List
rolePermissionList.stream().map(RolePermissionPo::getPermissionId).coll
ect(Collectors.toList());
List
permissionService.listByIds(permissionIds);
if (permissionList.isEmpty()) {
return user;
}
// 构建权限集合
Set
// 添加权限
for (PermissionPo permission : permissionList) {
authorities.add(new
SimpleGrantedAuthority(permission.getCode()));
}
// 添加角色(作为权限)
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority("ROLE_" +
role));
}
return new
org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(), true,
// enabled
true, // accountNonExpired
true, // credentialsNonExpired
true, // accountNonLocked
authorities);
Spring Security 通过多个内置的 Servlet Filter 实现不同的安全功能,如认证、授权、CSR
F、防止会话固定攻击等。
SecurityFilterChain 负责把这些过滤器按顺序组装起来,组成一个完整的处理流程。
配置白名单
配置jwt来源的header是x-www-iap-assertion
配置什么来处理jwt
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception {
http
.sessionManagement(sessionManagement ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS
)
)
.csrf(AbstractHttpConfigurer::disable)
.cors(Customizer.withDefaults())
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(AUTH_WHITELIST).permitAll()
.requestMatchers("/card/sync").permitAll()
.requestMatchers("/test/**").permitAll()
.requestMatchers("/app/teacher/getSelfFeedbackInfo").permitAll()
.requestMatchers("/web/common/getDeliverRole/*").permitAll()
.requestMatchers("/web/common/getDeliverRole/**").permitAll()
.requestMatchers("/web/common/getTeamRole/*").permitAll()
.requestMatchers("/web/common/getTeamRole/**").permitAll()
.requestMatchers("/app/common/getMerchantWhiteList").permitAll()
.requestMatchers("/app/common/getMerchantWhiteList/*").permitAll()
.requestMatchers("/app/wechat/**").permitAll()
.requestMatchers("/web/experience/meetingDetail/**").permitAll()
.requestMatchers("/web/experience/meetingDetail").permitAll()
.requestMatchers("/web/student/contact/info/getSubscribeQrCode").permit
All()
jwt解码
如果jwt解析出现问题可以去找下configValues.getPublicKey,看下是否与生成的一致,
configValues.getPublicKey是nacos中公共的配置
上面配置的也就是这个解码器
.requestMatchers("/web/experience/historyChatInfoDetail").permitAll()
.requestMatchers("/web/common/getTeamRole/*").permitAll()
.requestMatchers("/web/common/getTeamRole/**").permitAll()
.requestMatchers("/xrebel/**").permitAll()
.requestMatchers("/app/im/teacherCourseStart").permitAll()
.requestMatchers("/app/teacher/getTeacherReviewNotStudyListNewKs").perm
itAll()
.requestMatchers("/web/teacher/register").permitAll()
.anyRequest().authenticated()
)
//可以使用 OAuth2 Resource Server 模式,成为oauth2资源服务器
//会自动启用 BearerTokenAuthenticationFilter,从请求中提取
Token 并处理认证流程
.oauth2ResourceServer(oauth2 -> oauth2
//自定义认证失败处理器
.authenticationEntryPoint(new
CustomerBearerAuthenticationEntryPoint())
//设置token的请求头
.bearerTokenResolver(new
HeaderBearerTokenResolver("x-www-iap-assertion"))
//设置jwt解析
.jwt(jwt -> jwt
//jwt转换,及权限
.jwtAuthenticationConverter(grantedAuthoritiesExtractor())
//解码器
.decoder(myCustomDecoder())
)
);
return http.build();
}
private JwtDecoder myCustomDecoder() {
配置jwt处理逻辑
这个也是上面的配置grantedAuthoritiesExtractor()
jwt解析后的claim
jwt是三段式,其中存储用户的有效信息都在claim中,如下
PublicKey publicKey = SecureUtil.generatePublicKey("RSA",
Base64.decode(configValues.getPublicKey()));
NimbusJwtDecoder build =
NimbusJwtDecoder.withPublicKey((RSAPublicKey) publicKey).build();
return build;
}
/**
*/
public CustomerJwtConverter grantedAuthoritiesExtractor() {
CustomerJwtConverter customerJwtConverter = new
CustomerJwtConverter();
customerJwtConverter.setJwtGrantedAuthoritiesConverter(
new GrantedAuthoritiesExtractor(dxEventBus));
return customerJwtConverter;
}
{
"cus": "{\"isDjj\":false,\"lt\":1}",
"sub": "dxadmin",
"role": "admin",
"iss": "http://127.0.0.1:10111/new/security",
"authorities": [
"dx:deliver:all"
],
"aud": "DELIVER",
"uid": "798368854534529062",
"nbf": 1752721709,
"identity": "dx_znyy_resource",
"scope": [
"dx:deliver:all"
],
"exp": 2091121709,
"iat": 1752721709,
"acType": 8,
"ucode": "A0001",
"jti": "16ffdffb-8e9a-4dd5-8bb5-36ac7ece6fe5"
}
jwt具体的处理逻辑
基本就是从json中把数据取出来,如果没有就抛出异常
返回最终认证的token(isAuthenticated(),是否认证)
因为是已经认证的token,不会再有provider处理,后续直接从这个token中获取用户信息
public class CustomerJwtConverter implements Converter
AbstractAuthenticationToken> {
private Converter
jwtGrantedAuthoritiesConverter =
new JwtGrantedAuthoritiesConverter();
@Override
public AbstractAuthenticationToken convert(Jwt jwt) {
Map
Object sub = claims.get("sub");
if (!(sub instanceof String)) {
throw new JwtValidationException(
"token is not valid",
Collections.singletonList(new
OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT)));
}
Object uid = claims.get("uid");
if (uid == null) {
throw new JwtValidationException(
"token is not valid",
Collections.singletonList(new
OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT)));
}
Object role = claims.get("role");;
if (role == null) {
throw new JwtValidationException(
"token is not valid",
Collections.singletonList(new
OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT)));
}
Object identity = claims.get("identity");
Object acTypeO = claims.get("acType");
Object ucode0 = claims.get("ucode");
Object cusO = claims.get("cus");
String userRole = String.valueOf(role);
String subStr = String.valueOf(sub);
String uidStr = String.valueOf(uid);
String ucode = ucode0 == null ? null : String.valueOf(ucode0);
String cus = cusO == null ? null : String.valueOf(cusO);
DxjyAuthenticationToken
来看下这个token,在创建这个对象的时候,就设置了setAuthenticated为true,已经认证
并且设置了需要的用户信息deliverSecurityUser
所以返回这个token即为jwt认证的终点,后续只要从这个token中拿想要的用户信息即可
Integer acType = acTypeO == null ? null : ((Long)
acTypeO).intValue();
String identityStr = identity == null ? null :
String.valueOf(identity);
Collection
extractAuthorities(jwt);
DeliverSecurityUser deliverSecurityUser = new
DeliverSecurityUser();
deliverSecurityUser.setId(uidStr);
deliverSecurityUser.setUcode(ucode);
deliverSecurityUser.setUsername(subStr);
deliverSecurityUser.setPassword("");
deliverSecurityUser.setIdentity(identityStr);
deliverSecurityUser.setAuthorities(new ArrayList<>
(authorities));
deliverSecurityUser.setAcType(acType);
deliverSecurityUser.setUserRole(userRole);
if (StringUtils.isNotBlank(cus)) {
deliverSecurityUser.setCustomParam(GsonUtils.getGson().fromJson(cus,
CustomParam.class));
}
return new DxjyAuthenticationToken(authorities,
deliverSecurityUser);
}
public void setJwtGrantedAuthoritiesConverter(
Converter
jwtGrantedAuthoritiesConverter) {
this.jwtGrantedAuthoritiesConverter =
jwtGrantedAuthoritiesConverter;
}
protected Collection
{
return this.jwtGrantedAuthoritiesConverter.convert(jwt);
}
}
工具类中获取当前登录用户信息
public class DxjyAuthenticationToken extends
AbstractAuthenticationToken {
private final DeliverSecurityUser deliverSecurityUser;
/**
*
for the principal
*/
public DxjyAuthenticationToken(Collection
GrantedAuthority> authorities, DeliverSecurityUser
deliverSecurityUser) {
super(authorities);
super.setAuthenticated(true);
this.deliverSecurityUser = deliverSecurityUser;
}
@Override
public Object getCredentials() {
return deliverSecurityUser;
}
@Override
public Object getPrincipal() {
return deliverSecurityUser;
}
}
/**
*
com.dxznjy.framework.common.mode.security.DxjySecurityUser
*/
public static DeliverSecurityUser getLoginUser() {
Object principal =
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (!(principal instanceof DeliverSecurityUser)) {
开发获取当前登录人的用户信息
token是否可以跨服务使用?
可以,token的格式基本一致,jwt的解码key也是公共的
如果没有特殊校验,比如会员的token可以用来访问deliver中的请求
可以单独添加上权限控制
添加权限
GrantedAuthoritiesExtractor
这个是和的解析jwt一起配置的,目的是为了配置权限
在上面CustomerJwtConverter解析jwt过程中,获取权限会执行这份代码
根据角色查询权限,后面又统一封拼接上字符串ROLE_ ,也就变成了角色(挺神奇的做法)
throw new InvalidBearerTokenException("Token无效");
}
DeliverSecurityUser securityUser = (DeliverSecurityUser)
principal;
return securityUser;
}
DeliverSecurityUser loginUser = SpringSecurityUtils.getLoginUser();
public class GrantedAuthoritiesExtractor implements Converter
Collection
private final DxEventBus dxEventBus;
public GrantedAuthoritiesExtractor(DxEventBus dxEventBus) {
this.dxEventBus = dxEventBus;
}
@Override
public Collection
Map
Object uid = claims.get("uid");
Object acTypeO = claims.get("acType");
Object ucode0 = claims.get("ucode");
Object role = claims.get("role");
String ucode = ucode0 == null ? null : String.valueOf(ucode0);
Response fire = dxEventBus.fire(new
UserAccessQueryRoleEvent(ucode, ((Long) acTypeO).intValue(),
role == null ? null : (String) role,
String.valueOf(claims.get("jti"))));
UserAccessQueryRoleEventHandler
上面触发的事件,UserAccessQueryRoleEvent得到处理,这里查询具体的数据库然后返回具体的权
限返回
if (!(fire instanceof MultiResponse)) {
throw new RuntimeException(fire.getErrMessage());
}
MultiResponse
if (!perms.isSuccess()) {
throw new RuntimeException(perms.getErrMessage());
}
Set
for (String datum : perms.getData()) {
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_"
+ datum));
}
return grantedAuthorities;
}
}
@EventHandler
public class UserAccessQueryRoleEventHandler
implements EventHandlerI
UserAccessQueryRoleEvent> {
@Autowired
private SysRoleService sysRoleService;
@Autowired
private SysRolePermRepository sysRolePermRepository;
@Autowired
private SysPermRepository sysPermRepository;
@Override
@Transactional(rollbackFor = Exception.class)
public MultiResponse
event) {
if (StringUtils.isEmpty(event.getRole())) {
return
MultiResponse.buildFailure(CommonCodeEnum.UNAUTHORISE.code(),
CommonCodeEnum.UNAUTHORISE.message());
}
Set
if(StringUtils.isBlank(event.getUserCode())){
权限的一些问题:
为什么按照权限来控制不管用?
为什么@PreAuthorize("hasAuthority('m:refundReview:index')") 不管用,因为获
取到权限后统一的前面都加上了ROLE_ 前缀,也就是说在spring-securtiy中现在只有角色而没有
权限,所以正确的写法应该是@PreAuthorize(value = "hasRole('m:refundReview:in
dex')")
人物有多角色吗?
没有,上面获取角色只是当string来获取再查询的,如果是list肯定不兼容
return MultiResponse.of(roles, 0);
}
String role = event.getRole();
SysRole sysRole = sysRoleService.selectRoleByValue(role);
List
if(ObjectUtil.isNotNull(sysRole)){
Set
sysRolePermRepository.findAllByRoleId(sysRole.getId()).stream()
.map(SysRolePerm::getPermId)
.collect(Collectors.toSet());
List
sysPermRepository.findAllById(perIds);
if (StringUtils.equals(role,
RoleTagEnum.JIAOFUMANAGER.getValue()) || StringUtils.equals(role,
RoleTagEnum.ADMIN.getValue())) {
sysPermList = sysPermRepository.findAll();
authVos = sysPermList.stream().map(perd -> new
AuthVo(perd.getPname(), perd.getPval())).collect(Collectors.toList());
}
for (SysPerm per : sysPermList) {
//如果是超级管理员赋予所有权限
authVos.add(new AuthVo(per.getPname(), per.getPval()));
}
authVos.add(new AuthVo("角色权限标识", role));
}
if (DxArraysUtils.isBlank(authVos)) {
return MultiResponse.of(roles, 0);
} else {
roles =
authVos.stream().map(AuthVo::getVal).collect(Collectors.toSet());
return MultiResponse.of(roles, roles.size());
}
}
}
登录的时候也都是选角色登录的
登录的时候是在security工程,而权限设置是在deliver工程中,security中并没有存储角色和权限,角
色是怎么在security工程中设置的?
登录的doLogin方法中通过dubbo接口获取登录名对应的角色
相关表
鼎校登录
相关表
统一登录
登录的逻辑统一走的是dingxiao-service-security-authorizer-client工程
而各种登录基本都是统一走的同一个逻辑
统一的登录逻辑
基本步骤
--交付系统用户
sys_user
--角色员权限关联表
sys_role_perm
--权限表
sys_perm
--用户角色关联表
sys_user_role
--角色表
sys_role
--用户表(用来查询登录的具体用户)
security_oauth2_user
--注册的客户端
security_oauth2_client
各种登录,封装参数
↓
调用 doLogin 方法
↓
/oauth2/token接口
这个 URL 是 Spring Security OAuth2 的标准 Token 接口(/oauth2/token),用于通过密码模式
(grant_type=password)获取访问令牌(access_token)。
通过构造这个 URL 发起 OAuth2 密码模式认证请求,最终由 Spring Security OAuth2 模块处理并
返回 Token。
最终会构造UsernamePasswordAuthenticationToken,然后走DaoAuthenticationProvider,后面是U
serDetailsServiceImpl的loadUserByUsername
请求中username是一个json的字符串,因为最终的loadUserByUsername方法的参数只有一个user
name,所以需要的额外信息只能放一起组成json再转string
处理一些前置逻辑,处理重写密码逻辑,获取客户端密码
↓
构造 OAuth2 请求(/oauth2/token)(这个路径是spring-security自带的,其实现最终会
走到UserDetailsServiceImpl)
↓
发送请求获取访问令牌(access_token)
↓
解析响应结果
↓
根据用户类型调用回调方法(如学生、会员、商户等)
↓
返回登录结果(包含 access_token)
public ResultUtil
userNameJsonModel, String password) {
String clientId = userNameJsonModel.getDs().getAppSource();
// 根据client标识查询已注册的客户端
CustomerRegisteredClient customerRegisteredClient =
customerRegisteredClientService.findByCusClientId(clientId);
if (customerRegisteredClient == null) {
// 若查询不到已注册的客户端,则返回失败结果
return ResultUtil.fail("无效的client标识");
}
if (StringUtils.isNotEmpty(userNameJsonModel.getRewritePwd()))
{
// 若存在重写密码,则将密码替换为重写密码
password = userNameJsonModel.getRewritePwd();
}
// 获取客户端密码
String clientPassword = clientPasswordMap.get(clientId);
if (clientPassword == null) {
// 若客户端密码为空,则使用AES加密算法对客户端秘钥进行加密后存入
clientPasswordMap中
byte[] decode =
Base64.decode(customerRegisteredClient.getPlainSecret());
clientPassword =
SecureUtil.aes(configValues.getClientAesKeyBytes()).decryptStr(decode);
clientPasswordMap.put(clientId, clientPassword);
}
// 设置请求头
HttpHeaders requestHeaders = new HttpHeaders() ;
requestHeaders.set("Authorization", "Basic " +
Base64.encode(clientId + ":" + clientPassword));
// 创建请求实体
HttpEntity
(requestHeaders);
// 将用户名及密码序列化为JSON字符串
String jsonString = JSON.toJSONString(userNameJsonModel);
// 获取客户端请求的scope
String scpoe =
customerRegisteredClient.getRegisteredClient().getScopes().stream().fin
dFirst().orElseThrow(() -> new RuntimeException("client没有scope配置"));
// 构建请求URL
String url = String.format("http://127.0.0.1:%s%s/oauth2/token?
grant_type=password&username=%s&password=%s&scope=%s",
port, contextPath,
Base64.encode(URLUtil.encode(jsonString)), password,
URLUtil.encode(scpoe));
// 发送POST请求获取访问令牌
ResponseEntity
restTemplate.exchange(url,
HttpMethod.POST, requestEntity, String.class, "");
// 若返回状态码不为200,返回失败结果
if (!postForEntity.getStatusCode().equals(HttpStatus.OK)) {
return ResultUtil.fail(LoginCodeEnum.LOGIN_FAIL.code(),
LoginCodeEnum.LOGIN_FAIL.message());
}
// 获取返回的响应体
String body = postForEntity.getBody();
// 若响应体为空或无效,则返回失败结果
if (StringUtils.isEmpty(body)) {
return ResultUtil.fail(LoginCodeEnum.LOGIN_FAIL.code(),
LoginCodeEnum.LOGIN_FAIL.message());
}
// 解析响应体,将其转换为ResultUtil对象
ResultUtil
JSON.parseObject(body, new TypeReference<>() {
});
// 若解析结果为空,则返回失败结果
if (resultUtil == null) {
return ResultUtil.fail(LoginCodeEnum.LOGIN_FAIL.code(),
LoginCodeEnum.LOGIN_FAIL.message());
}
// 若解析结果中的success属性为false,则直接返回解析结果
if (!resultUtil.isSuccess()) {
return resultUtil;
}
// 获取AccessTokenResult对象
AccessTokenResult tokenResult = resultUtil.getData();
// 若tokenResult为空,则返回失败结果
if (tokenResult == null) {
return ResultUtil.fail(LoginCodeEnum.LOGIN_FAIL.code(),
LoginCodeEnum.LOGIN_FAIL.message());
}
if (userNameJsonModel.getSkipNotify() != null &&
userNameJsonModel.getSkipNotify()) {
// 若skipNotify为true,则直接返回访问令牌结果
return ResultUtil.success(tokenResult);
}
// 存入JWT令牌(可以用来取数据)
if (StringUtils.isNotEmpty(tokenResult.getToken())) {
SingleResponse objectSingleResponse =
SingleResponse.buildFailure(LoginCodeEnum.LOGIN_FAIL.code(),
LoginCodeEnum.LOGIN_FAIL.message());
if (userNameJsonModel.getUserType() ==
UserTypeID.STUDENT.getValue()) {
// 若用户类型为学生,则调用学生登录成功回调方法
if(AppSource.ZHEN_XUAN.equals(userNameJsonModel.getDs().getAppSource())
){
// 如果是发起登录的源终端是甄选则需要通知甄选登录成功回调方法
objectSingleResponse =
zxLoginNofiyI.studentLoginSuccessCallBack(userNameJsonModel);
}else{
objectSingleResponse =
znyyLoginNofiyI.studentLoginSuccessCallBack(userNameJsonModel);
}
} else if (userNameJsonModel.getUserType() ==
UserTypeID.MEMBER.getValue()) {
// 若用户类型为会员,则调用会员登录成功回调方法
objectSingleResponse =
znyyLoginNofiyI.userLoginSuccessCallBack(userNameJsonModel);
} else if (userNameJsonModel.getUserType() ==
UserTypeID.MERCHANT.getValue()) {
// 若用户类型为商户,则调用管理员登录成功回调方法
objectSingleResponse =
znyyLoginNofiyI.adminLoginSuccessCallBack(userNameJsonModel);
} else if (userNameJsonModel.getUserType() ==
UserTypeID.DELIVER.getValue()) {
// 若用户类型为交付,则调用交付登录成功回调方法
objectSingleResponse =
deliverLoginUserCall.userLoginSuccessCallBack(userNameJsonModel);
} else if (userNameJsonModel.getUserType() ==
UserTypeID.ZHEN_XUAN_ADMIN.getValue()){
// 若用户类型为甄选后台用户,则调用甄选后台成功回调方法
objectSingleResponse =
zxAdminNofiyI.userLoginSuccessCallBack(userNameJsonModel);
}else if(userNameJsonModel.getUserType() ==
UserTypeID.SCRM.getValue()){
// 若登录来源是SCRM系统,则调用Scrm登录成功回调,验证图片验证码
// znyyLoginNofiyI =
DubboCallUtil.getProxy(LoginNotifyI.class, AppSource.SCRM, false);
objectSingleResponse =
scrmLoginNofiyI.userLoginSuccessCallBack(userNameJsonModel);
} else if (userNameJsonModel.getUserType() ==
UserTypeID.TRAIN.getValue()) {
objectSingleResponse =
trainLoginNotifyI.userLoginSuccessCallBack(userNameJsonModel);
} else if(userNameJsonModel.getUserType() ==
UserTypeID.SCRM_V2.getValue()){
// 若登录来源是SCRM系统,则调用Scrm登录成功回调,验证图片验证码
// znyyLoginNofiyI =
DubboCallUtil.getProxy(LoginNotifyI.class, AppSource.SCRM, false);
objectSingleResponse =
scrmLoginV2NofiyI.userLoginSuccessCallBack(userNameJsonModel);
}else if(userNameJsonModel.getUserType() ==
UserTypeID.IM.getValue()){
//todo ()im 登录回调处理
objectSingleResponse =
imLoginV2NofiyI.userLoginSuccessCallBack(userNameJsonModel);
}
// 若回调方法返回结果不成功,则返回失败结果
if (!objectSingleResponse.isSuccess()) {
return
ResultUtil.response(DxResponseUtils.changeData(null,
objectSingleResponse));
}
// 若回调方法成功并返回了数据,则将其存入tokenResult的回调数据中
oauth2/token后续处理逻辑
前面得知登录其实都是走的统一的接口,然后又调用oauth2/token这个统一的oauth2.0登录接口,返
回token
所以接下来分下这个aouth2.0的处理流程
authorizationServerSecurityFilterChain
配置用于oauth2流程的处理逻辑的过滤器(虽然spring-security帮我们实现了接口,但是具体实现要
我们要配置并实现)
这个 SecurityFilterChain 使用了 OAuth2AuthorizationServerConfigurer,它是 Spring Security 官
方提供的专门用于配置 OAuth2 授权服务器的配置器,只处理 OAuth2 相关的请求
其中使用了PasswordAuthenticationConverter来处理http请求
if (objectSingleResponse.isSuccess() &&
objectSingleResponse.getData() != null) {
tokenResult.setCallbackData(objectSingleResponse.getData());
}
}
@Bean
@Order(1)
public SecurityFilterChain
authorizationServerSecurityFilterChain(HttpSecurity http,
AuthenticationManager authenticationManager,
OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator
> tokenGenerator) throws Exception {
OAuth2AuthorizationServerConfigurer
authorizationServerConfigurer = new
OAuth2AuthorizationServerConfigurer();
authorizationServerConfigurer.tokenEndpoint(oAuth2TokenEndpointConfigur
er -> {
oAuth2TokenEndpointConfigurer.
accessTokenRequestConverters(authenticationConverters -
> authenticationConverters.add(new PasswordAuthenticationConverter()))
.authenticationProviders(authenticationProviders ->
authenticationProviders.add(new
PasswordAuthenticationProvider(authenticationManager,
authorizationService, tokenGenerator)))
.accessTokenResponseHandler(new
MyAuthenticationSuccessHandler())
.errorResponseHandler(new
MyAuthenticationFailureHandler());
PasswordAuthenticationConverter
再看下这个converter的逻辑,就是根据request对象,生成PasswordAuthenticationToken
后面也是熟悉的逻辑根据这个token类型寻找对应的provider,然后处理token,设置用户信息授权等
});
http.apply(authorizationServerConfigurer);
http.cors().disable().csrf().disable();
http.authorizeHttpRequests(auth -> auth
.requestMatchers(AUTH_WHITELIST).permitAll()
.anyRequest().authenticated());
return http.build();
}
public class PasswordAuthenticationConverter implements
AuthenticationConverter {
@Override
public Authentication convert(HttpServletRequest request) {
String grantType =
request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
if
(!AuthorizationGrantType.PASSWORD.getValue().equals(grantType)) {
return null;
}
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
MultiValueMap
RequestUtils.getQueryParams(request);
String scope =
queryParams.getFirst(OAuth2ParameterNames.SCOPE);
if (StringUtils.isEmpty(scope)) {
OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_SCOPE, "scope为
空",
scope);
}
Set
((StrUtil.split(scope, " ")));
// 用户名验证(必需)
String username =
queryParams.getFirst(OAuth2ParameterNames.USERNAME);
if (StrUtil.isBlank(username)) {
OAuth2EndpointUtils.throwError(
LoginCodeEnum.USERNAME_PASSWORD_IS_NOT_VALID.code().toString(),
UserDetailsServiceImpl
之后还是转换成了UsernamePasswordAuthenticationToken,走到了熟悉的UserDetailsServiceImp
l
根据不同方式选择不同登录的具体实现,但是统一都是返回userDetails
LoginCodeEnum.USERNAME_PASSWORD_IS_NOT_VALID.message(),
"用户名为空"
);
}
// 密码验证(必需)
String password =
queryParams.getFirst(OAuth2ParameterNames.PASSWORD);
if (StrUtil.isBlank(password)) {
OAuth2EndpointUtils.throwError(
LoginCodeEnum.USERNAME_PASSWORD_IS_NOT_VALID.code().toString(),
LoginCodeEnum.USERNAME_PASSWORD_IS_NOT_VALID.message(),
username
);
}
// 附加参数(保存用户名/密码传递给 PasswordAuthenticationProvider 用
于身份认证)
Map
.entrySet()
.stream()
.filter(e ->
!e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE) &&
!e.getKey().equals(OAuth2ParameterNames.SCOPE)
).collect(Collectors.toMap(Map.Entry::getKey, e ->
e.getValue().get(0)));
return new PasswordAuthenticationToken(authentication,
requestedScopes, additionalParameters);
}
@Override
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
if (StringUtils.isEmpty(username)) {
OAuth2EndpointUtils.throwError(LoginCodeEnum.USERNAME_PASSWORD_IS_NOT_V
ALID.code().toString(), LoginCodeEnum.ACCOUNT_NO_LOGIN_ROLE.message(),
"");
查询security_oauth2_user,得到用户
查看其中一个逻辑,就是去数据库查询用户,组装返回userDetails对象
得到SecurityOauth2User,后面对它进行封装成userDetails,进行spring-security的校验,通过即登录
成功
根据这个查询方法,也解答了一个问题,为什么userType不同,同样的密码为什么登录不上,因为在系
统中记录就不是"同一个人"
}
String decodeStr = Base64.decodeStr(username);
decodeStr = URLUtil.decode(decodeStr);
if (!JSON.isValid(decodeStr)) {
OAuth2EndpointUtils.throwError(LoginCodeEnum.LOGIN_PARAMS_ERROR.code().
toString(), LoginCodeEnum.LOGIN_PARAMS_ERROR.message(), username);
}
UserNameJsonModel nameJsonModel =
JSON.parseObject(decodeStr, UserNameJsonModel.class);
CustomerUserDetails userDetails = null;
// 用户微信用户名密码登录
if (nameJsonModel.getCustomParam().getLt() == 4) {
userDetails = this.doThirdPasswordLogin(nameJsonModel);
} else if (nameJsonModel.getCustomParam().getLt() == 5) {
userDetails = this.doStudentMemberPwdLogin(nameJsonModel);
} else if (nameJsonModel.getCustomParam().getLt() == 6) {
/*用户使用第三方授权登陆,无用户名无密码*/
userDetails =
this.doAppNoUserNameAndNoPwdLogin(nameJsonModel);
} else if
(StringUtils.isNotBlank(nameJsonModel.getRewritePwd())) {
userDetails =
this.getUserDetailsUseRewritePwd(nameJsonModel);
} else if (StringUtils.isNotBlank(nameJsonModel.getMemName()))
{
userDetails = getUserDetailsUseUseMemberPwd(nameJsonModel);
} else {
userDetails = getUserDetails(nameJsonModel);
}
private SecurityOauth2User getSecurityOauth2User(
String username, Integer userType) {
MultiResponse
securityOauth2UserService.list(username, userType, null);
if (!securityOauth2Users.isSuccess()) {
OAuth2EndpointUtils.throwError(CommonCodeEnum.SERVER_ERROR.code().toStr
在返回userDetails后,也就是spring-security的校验了
查询各系统中用户信息
调用buddo接口查询各系统中用户信息,其中包括userCode和role
这个接口是多实现的,根据传入的参数,动态判断,所属 用户是属于哪个系统的
后面会将这部分放到userDetails中
各种登录有什么区别?
看完上面得知各种登录其实都走的统一的dologin 方法,而不同的登录也就是调用这个方法也就是
组装的参数不同
后面登录逻辑是根据不同参数走不同的分支
也就是说,找出关键参数,也就能区分各种登录
userType
在security工程中根据username和userType找人
这个也决定了去哪个系统中找对应的人
ing(), securityOauth2Users.getErrMessage(), username);
}
List
securityOauth2Users.getData();
if (securityOauth2UsersData == null ||
securityOauth2UsersData.isEmpty()) {
OAuth2EndpointUtils.throwError(LoginCodeEnum.ACCOUNT_DOES_NOT_EXIST.cod
e().toString(), LoginCodeEnum.ACCOUNT_DOES_NOT_EXIST.message(),
username);
}
SecurityOauth2User oauth2User =
Objects.requireNonNull(securityOauth2UsersData).get(0);
return oauth2User;
}
LoginUserCallI loginUserCallI =
DubboCallUtil.getProxy(LoginUserCallI.class, group, false);
SingleResponse
loginUserCallI.getUserRoleByLoginName(nameJsonModel.getUname(),
nameJsonModel.getCustomParam().getRoleSec());
MultiResponse
securityOauth2UserService.list(username, userType, null);
可以看一个案例
userDetailServiceImpl 中根据userType判断找对应的实现类(后面这个实现类获取角色),
很明显这个会请求deliver的dubbo接口
DxSource
组成分别是
appSource-应用来源
terminalSource-终端来源
viewSource-呈现终端
doLogin中根据appSource找注册的客户端,后面会构造请求头,设置scope
也有直接用appSource来找具体的实现类(请求各系统中用户信息)
public enum UserTypeID {
STUDENT(6, "学员"),
MERCHANT(8, "商户"),
MEMBER(9, "会员"),
DELIVER(7, "交付系统"),
SCRM(10, "SCRM系统"),
SCRM_V2(11, "SCRM_V2系统"),
ZHEN_XUAN_ADMIN(12, "甄选后台"),
TRAIN(13, "学习中心"),
IM(14, "IM系统后台")
if (nameJsonModel.getUserType() == UserTypeID.DELIVER.getValue() &&(
nameJsonModel.getCustomParam().getMeetingUserType() == 2)){
LoginUserCallI loginUserCallI =
DubboCallUtil.getProxy(LoginUserCallI.class, AppSource.DELIVER,
false);
DELIVER##WX##MINIAPP
String clientId = userNameJsonModel.getDs().getAppSource();
// 根据client标识查询已注册的客户端
CustomerRegisteredClient customerRegisteredClient =
customerRegisteredClientService.findByCusClientId(clientId);
}else if (nameJsonModel.getDs().getAppSource().equals(AppSource.IM)) {
group = nameJsonModel.getDs().getAppSource();
}
CustomParam
其中重点关注下lt 参数和role 参数,控制着登录方式
如果lt不同,则,登录的逻辑不同
if (StringUtils.isNotEmpty(nameJsonModel.getRole())) {
LoginUserCallI loginUserCallI =
DubboCallUtil.getProxy(LoginUserCallI.class, group, false);
public class CustomParam implements Serializable {
@Tolerate
public CustomParam() {
}
/**
*/
private String dd;
/**
*/
private String mc;
/**
名密码登录 5学员使用会员账户登录,6 app调用第三方绑定授权登陆
*/
private Integer lt;
/**
*/
private String wxo;
......(省略)
}
if (nameJsonModel.getCustomParam().getLt() == 4) {
userDetails = this.doThirdPasswordLogin(nameJsonModel);
} else if (nameJsonModel.getCustomParam().getLt() == 5) {
userDetails = this.doStudentMemberPwdLogin(nameJsonModel);
} else if (nameJsonModel.getCustomParam().getLt() == 6) {
/*用户使用第三方授权登陆,无用户名无密码*/
userDetails =
this.doAppNoUserNameAndNoPwdLogin(nameJsonModel);
} else if
(StringUtils.isNotBlank(nameJsonModel.getRewritePwd())) {
本文整理自实践经验,如有问题欢迎交流讨论。