- 使用自定义UserDetail
This commit is contained in:
yulinling 2025-06-15 23:20:28 +08:00
parent 5a6402f7fd
commit 8935c9f0c5
11 changed files with 153 additions and 40 deletions

View File

@ -5,7 +5,6 @@ import asia.yulinling.workflow.security.JwtAuthenticationEntryPoint;
import asia.yulinling.workflow.security.JwtAuthenticationFilter;
import asia.yulinling.workflow.security.JwtUserDetailsService;
import asia.yulinling.workflow.utils.JwtUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
@ -48,7 +47,7 @@ public class SecurityConfig {
// 认证请求
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login").permitAll()
.requestMatchers("/users", "/users/**").hasRole("ADMIN")
// .requestMatchers("/users", "/users/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
// JWT过滤器

View File

@ -3,8 +3,11 @@ package asia.yulinling.workflow.mapper;
import asia.yulinling.workflow.model.entity.Permission;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* <p>
*
@ -16,4 +19,5 @@ import org.springframework.stereotype.Component;
@Mapper
@Component
public interface PermissionMapper extends BaseMapper<Permission> {
List<Permission> selectPermissionsByRoleId(@Param("roleIds") List<Long> roleIds);
}

View File

@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* <p>
* 角色Mapper
@ -16,4 +18,5 @@ import org.springframework.stereotype.Component;
@Mapper
@Component
public interface RoleMapper extends BaseMapper<Role> {
List<Role> selectRoleByUserId(Long userId);
}

View File

@ -0,0 +1,19 @@
package asia.yulinling.workflow.mapper;
import asia.yulinling.workflow.model.entity.RolePermission;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
/**
* <p>
* 角色权限类
* </p>
*
* @author YLL
* @since 2025/6/15
*/
@Mapper
@Component
public interface RolePermissionMapper extends BaseMapper<RolePermission> {
}

View File

@ -0,0 +1,19 @@
package asia.yulinling.workflow.mapper;
import asia.yulinling.workflow.model.entity.RoleUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
/**
* <p>
* 角色用户类
* </p>
*
* @author YLL
* @since 2025/6/15
*/
@Mapper
@Component
public interface RoleUserMapper extends BaseMapper<RoleUser> {
}

View File

@ -10,6 +10,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@ -30,6 +31,7 @@ import java.util.stream.Collectors;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Slf4j
public class UserPrincipal implements UserDetails {
/**
@ -104,7 +106,19 @@ public class UserPrincipal implements UserDetails {
*/
private Collection<? extends GrantedAuthority> authorities;
public UserPrincipal(Long id, String username, String password, String nickname, String phone, String email, String birthday, Integer sex, Integer status, Date createTime, Date updateTime, List<String> roleNames, List<GrantedAuthority> authorities) {
public UserPrincipal(Long id,
String username,
String password,
String nickname,
String phone,
String email,
String birthday,
Integer sex,
Integer status,
Date createTime,
Date updateTime,
List<String> roleNames,
List<GrantedAuthority> authorities) {
this.id = id;
this.username = username;
this.password = password;
@ -125,22 +139,52 @@ public class UserPrincipal implements UserDetails {
return List.of();
}
public UserPrincipal(Long id, String username, String password,
List<String> roles, Collection<? extends GrantedAuthority> authorities) {
public UserPrincipal(Long id,
String username,
String password,
List<String> roles,
Collection<? extends GrantedAuthority> authorities) {
this.id = id;
this.username = username;
this.password = password;
this.roles = roles;
this.authorities = authorities;
this.status = 1; // 启用
this.status = 1;
}
public static UserPrincipal create(User user, List<Role> roles, List<Permission> permissions) {
List<String> roleNames = roles.stream().map(Role::getName).collect(Collectors.toList());
public static UserPrincipal create(User user,
List<Role> roles,
List<Permission> permissions) {
List<GrantedAuthority> authorities = permissions.stream().filter(permission -> StrUtil.isNotBlank(permission.getPermission())).map(permission -> new SimpleGrantedAuthority(permission.getPermission())).collect(Collectors.toList());
log.info("roleNames{}\n{}authorities", roles, permissions);
return new UserPrincipal(user.getId(), user.getUsername(), user.getPassword(), user.getNickname(), user.getPhone(), user.getEmail(), user.getBirthday(), user.getSex(), user.getStatus(), user.getCreateTime(), user.getUpdateTime(), roleNames, authorities);
List<String> roleNames = roles
.stream()
.map(Role::getName)
.collect(Collectors.toList());
List<GrantedAuthority> authorities = permissions
.stream()
.filter(permission ->
StrUtil.isNotBlank(permission.getPermission()))
.map(permission ->
new SimpleGrantedAuthority(permission.getPermission()))
.collect(Collectors.toList());
return new UserPrincipal(
user.getId(),
user.getUsername(),
user.getPassword(),
user.getNickname(),
user.getPhone(),
user.getEmail(),
user.getBirthday(),
user.getSex(),
user.getStatus(),
user.getCreateTime(),
user.getUpdateTime(),
roleNames,
authorities);
}
@ -161,12 +205,12 @@ public class UserPrincipal implements UserDetails {
@Override
public boolean isAccountNonLocked() {
return true;
return UserDetails.super.isAccountNonLocked();
}
@Override
public boolean isCredentialsNonExpired() {
return true;
return UserDetails.super.isCredentialsNonExpired();
}
@Override

View File

@ -3,7 +3,6 @@ package asia.yulinling.workflow.security;
import asia.yulinling.workflow.exception.BaseException;
import asia.yulinling.workflow.model.ApiResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;

View File

@ -1,5 +1,6 @@
package asia.yulinling.workflow.security;
import asia.yulinling.workflow.model.vo.user.UserPrincipal;
import asia.yulinling.workflow.utils.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
@ -8,11 +9,8 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
@ -31,7 +29,7 @@ import java.io.IOException;
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsService userDetailsService;
private final JwtUserDetailsService jwtUserDetailsService;
@Override
protected void doFilterInternal(@NotNull HttpServletRequest request,
@ -46,11 +44,11 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
String username = jwtUtil.parseToken(token).getSubject();
// 3. 根据username验证password
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
log.info("userDetails: {}", userDetails);
UserPrincipal userPrincipal = jwtUserDetailsService.loadUserByUsername(username);
log.info("userPrincipal: {}", userPrincipal);
UsernamePasswordAuthenticationToken authenticationToken
= new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
= new UsernamePasswordAuthenticationToken(userPrincipal, null, userPrincipal.getAuthorities());
log.info("authenticationToken: {}", authenticationToken);
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

View File

@ -1,23 +1,22 @@
package asia.yulinling.workflow.security;
import asia.yulinling.workflow.mapper.PermissionMapper;
import asia.yulinling.workflow.mapper.RoleMapper;
import asia.yulinling.workflow.mapper.RoleUserMapper;
import asia.yulinling.workflow.mapper.UserMapper;
import asia.yulinling.workflow.model.entity.Permission;
import asia.yulinling.workflow.model.entity.Role;
import asia.yulinling.workflow.model.entity.User;
import asia.yulinling.workflow.model.vo.user.UserPrincipal;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.Service;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
@ -34,41 +33,45 @@ import java.util.stream.Collectors;
public class JwtUserDetailsService implements UserDetailsService {
private final UserMapper userMapper;
private final RoleMapper roleMapper;
private final RoleUserMapper roleUserMapper;
private final PermissionMapper permissionMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
public UserPrincipal loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 构建查找sql
LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery(User.class)
.eq(User::getUsername, username)
.or()
.eq(User::getEmail, username)
.or()
.eq(User::getPhone, username);
.eq(User::getUsername, username)
.or()
.eq(User::getEmail, username)
.or()
.eq(User::getPhone, username);
// 2. 查找用户
User user = userMapper.selectOne(queryWrapper);
if (user == null) {
log.error("未找到用户信息{}", username);
throw new UsernameNotFoundException("未找到用户信息:" + username);
}
log.info("username:{}", username);
// 3. 查找用户对应的角色
List<Role> roles = roleMapper.selectByIds(Collections.singleton(user.getId()));
List<Role> roles = roleMapper.selectRoleByUserId(user.getId());
if (roles.isEmpty()) {
log.error("未找到角色信息{}", roles);
throw new UsernameNotFoundException("未找到角色信息" + roles);
}
Set<GrantedAuthority> authorities = roles.stream()
.map((role) -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toSet());
List<Permission> permissions = permissionMapper.selectPermissionsByRoleId(roles.stream().map(Role::getId).collect(Collectors.toList()));
if (permissions.isEmpty()) {
log.error("未找到权限信息{}", permissions);
throw new UsernameNotFoundException("未找到权限信息" + permissions);
}
// 4. 返回User
return new org.springframework.security.core.userdetails.User(
username,
user.getPassword(),
authorities
);
return UserPrincipal.create(user, roles, permissions);
}
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="asia.yulinling.workflow.mapper.PermissionMapper">
<select id="selectPermissionsByRoleId" resultType="asia.yulinling.workflow.model.entity.Permission">
SELECT DISTINCT wp.*
FROM wk_permission wp
JOIN wk_role_permission wrp ON wp.id = wrp.permission_id
JOIN wk_role wr ON wr.id = wrp.role_id
WHERE wr.id in
<foreach collection="roleIds" item="roleId" open="(" separator="," close=")">
#{roleId}
</foreach>
</select>
</mapper>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="asia.yulinling.workflow.mapper.RoleMapper">
<select id="selectRoleByUserId" resultType="asia.yulinling.workflow.model.entity.Role">
SELECT DISTINCT r.*
FROM wk_role r
JOIN wk_role_user ur ON r.id = ur.role_id
JOIN wk_user u ON u.id = ur.user_id
WHERE u.id = #{userId}
</select>
</mapper>