springboot版本:2.4.3
创建项目啥的我就不说了,大家都看这个了,相信创建项目都轻而易举了,直接进入正题;
1:添加JWT和Spring Security依赖,多添加一个validation校验和lombok
io.jsonwebtoken
jjwt
0.9.1
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-validation
org.projectlombok
lombok
true
2:定义用户访问无权限资源时候的异常
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.setStatus(403);
PrintWriter printWriter = response.getWriter();
printWriter.write(new ObjectMapper().writeValueAsString(Result.fail("权限不足,请联系管理员!")));
printWriter.flush();
printWriter.close();
}
}
3:定义用户权限不足的异常
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.setStatus(403);
PrintWriter printWriter = response.getWriter();
printWriter.write(new ObjectMapper().writeValueAsString(Result.fail("权限不足,请联系管理员!")));
printWriter.flush();
printWriter.close();
}
}
4:Spring Security白名单类,放开权限控制不需要验证Token等信息
public class SpringSecurityConstant {
/**
* 放开权限校验的接口
*/
public static final String[] NONE_SECURITY_URL_PATTERNS = {
//后端的
"/login"
};
}
5:定义一个过滤器,登录时候先走这个类获取请求的token,具体业务逻辑因人而异
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private TokenUtil jwtTokenUtil;
@Autowired
private RedisUtil redisUtil;
@Autowired
private UserDetailsService userDetailsService;
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String authHeader = request.getHeader(tokenHeader);
if (!StrUtil.isBlank(authHeader)) {
String password = null;
try {
logger.debug("解析token");
password = jwtTokenUtil.getUserNameFromToken(authHeader);
} catch (Exception e) {
throw new RuntimeException("token解密失败");
}
// token存在,但是未登录
if (null != password && null == SecurityContextHolder.getContext().getAuthentication()) {
// 登录
logger.debug("登录loadUserByUsername的password");
UserDetails userDetails = userDetailsService.loadUserByUsername(password);
// 验证redis中的toekn是否过期,重新设置用户对象
if (redisUtil.hasKey(password)) {
String redisToken = (String) redisUtil.getObject(password);
// 判断携带的token和redis中的是否一致,一致则放行,不一致则以redis为准,退出请求
if (authHeader.equals(redisToken)) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
} else {
// 清除spring security用户认证信息
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (null != auth) {
new SecurityContextLogoutHandler().logout(request, response, auth);
}
}
// if (jwtTokenUtil.validateToken(authHeader, userDetails)) {
// UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
// authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// SecurityContextHolder.getContext().setAuthentication(authenticationToken);
// }
}
}
chain.doFilter(request, response);
}
}
6:定义SecurityConfig的配置类
@Component
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private JwtAccessDeniedHandler accessDeniedHandler;
/**
* 实现自定义登录逻辑
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//将登录的密码加密
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
}
/**
* 配置白名单
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.mvcMatchers(SpringSecurityConstant.NONE_SECURITY_URL_PATTERNS);
}
/* Security的核心配置 */
@Override
protected void configure(HttpSecurity http) throws Exception {
// 使用jwt,关闭csrf
http.csrf().disable();
// 基于token认证,关闭session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 配置白名单 除白名单以外的都进行认证
http.authorizeRequests().anyRequest().authenticated();
// 禁用缓存
http.headers().cacheControl();
// 添加jwt登录授权的过滤器
http.addFilterBefore(authenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
// 添加未授权和未登录的返回结果
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(authenticationEntryPoint);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public JwtAuthenticationFilter authenticationFilter() {
return new JwtAuthenticationFilter();
}
}
7:定义前端请求的实体类
@Data
@Api("系统用户登录参数")
public class SysLoginVo {
@ApiModelProperty(value = "登录账号",dataType = "String" ,required = true)
@NotBlank(message = "登录账号不能为空")
private String loginName;
@ApiModelProperty(value = "密码",dataType = "String" ,required = true)
@NotBlank(message = "密码不能为空")
private String password;
@ApiModelProperty(value = "用户类型",dataType = "String" ,required = true)
@NotBlank(message = "用户类型不能为空")
private String userType;
}
8:定义Entity实体类,实现UserDetails
@Component
@Data
public class SysUserEntity implements UserDetails{
private static final long serialVersionUID = 1L;
@ApiModelProperty("用户ID")
private Long userId;
@ApiModelProperty("部门ID")
private String deptId;
@ApiModelProperty("登录账号")
private String loginName;
@ApiModelProperty("用户类型(00系统用户 01注册用户)")
private String userType;
@ApiModelProperty("用户邮箱")
private String email;
@ApiModelProperty("手机号码")
private String phonenumber;
@ApiModelProperty("用户性别(0男 1女 2未知)")
private Character sex;
@ApiModelProperty("头像路径")
private String avatar;
@ApiModelProperty("密码")
private String password;
@ApiModelProperty("帐号状态(0正常 1停用)")
private String status;
@Override
public Collection extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
if(StringUtils.isNotBlank(this.getStatus()) && "0".equals(this.getStatus())) {
return false;
}
return true;
}
@Override
public String getUsername() {
return this.getLoginName();
}
}
9:定义Controller类,添加登录方法
@RestController
@Api("用户信息登录控制器")
public class LoginConyroller {
@Autowired
private UserService userService;
@Autowired
private RedisUtil redisUtil;
@PostMapping("/sysLogin")
@ApiOperation(value = "系统用户登录", httpMethod = "POST")
public Result sysLogin(@RequestBody @Valid SysLoginVo sysLoginVo) {
return userService.sysLogin(sysLoginVo);
}
@GetMapping("/logOut")
@ApiOperation(value = "退出登录", httpMethod = "GET")
public Result logOut(HttpServletRequest request, HttpServletResponse
response,Principal principal ) {
//清除spring security用户认证信息
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
//清除redis中的token信息
redisUtil.delKey(principal.getName());
if(null != auth) {
new SecurityContextLogoutHandler().logout(request, response, auth);
}
return new Result(MessageConstant.SUCCESS_CODE, MessageConstant.LOGOUT_SUCCESS);
}
}
10:定义userService接口
public interface UserService {
/**
* 登录
* @param loginVo
* @return
*/
Result sysLogin(SysLoginVo sysLoginVo);
}
11:定义UserServiceImpl 实现userService接口,完成登录的业务逻辑处理
@Service
public class UserServiceImpl implements UserService{
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public Result sysLogin(SysLoginVo sysLoginVo) {
//系统用户登录以*开头
String username = "*" + sysLoginVo.getLoginName();
//走SpringSecurity的方法
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
//利用hutool工具类转成数据库中的对象
SysUserEntity sysUserEntity = BeanUtil.copyProperties(userDetails, SysUserEntity.class);
//获取MD5加密值
String md5HexPassword = DigestUtils.md5Hex(sysLoginVo.getLoginName() + sysLoginVo.getPassword() + sysUserEntity.getSalt());
logger.debug("获取到md5加密值");
//拿着获取到的用户名和密码验证一遍
if(userMapper.checkSysUserLogin(sysLoginVo.getLoginName(),md5HexPassword) == 0 ) {
logger.debug("用户名:{}和密码:{}校验未通过",sysLoginVo.getLoginName(),md5HexPassword);
return Result.fail("用户名或密码错误");
};
//这里会调用SysUserEntity实体类中的isEnabled方法,根据业务逻辑自行判断账户状态
if(userDetails.isEnabled()) {
return Result.fail("该帐号已被禁用,请联系管理员!");
}
return baseLogin(userDetails,null, sysLoginVo,"sysUser");
}
@Transactional(rollbackFor = {Exception.class})
private Result baseLogin(UserDetails userDetails, LoginVo loginVo,SysLoginVo sysLoginVo, String userRole) {
HashMap
12:定义一个UserDetailsServiceImpl的类实现userService接口,完成自定义登录
@Service
public class UserDetailsServiceImpl implements UserDetailsService{
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private UserMapper userMapper;
@Autowired
private SysLogInFoMapper sysLogInfoMapper;
@Transactional(rollbackFor = Exception.class)
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 判断是小程序用户还是系统用户
// 小程序用户登录以#开头
if (username.startsWith("#")) {
String identityCard = username.substring(username.indexOf("#") + 1);
logger.debug("小程序用户身份证号为:{}", identityCard);
// 查询小程序用户,identityCard为传过来的身份证号
CreditCustomerEntity mpUser = userMapper.findByuserId(identityCard);
// 小程序用户为空则去查成员表
if (mpUser == null) {
// 将成员表信息新增到小程序用户表
Map map = userMapper.findByfrmMeber(identityCard);
if (map == null) {
throw new RuntimeException("当前用户信息不存在!");
}
map.put("login_time", sdf.format(new Date()));
userMapper.insertCreditCustomer(map);
logger.debug("小程序用户表新增信息成功:{}", map);
return userMapper.findByuserId(identityCard);
}
return mpUser;
} else if(username.startsWith("*")){
String loginName = username.substring(username.indexOf("*") + 1);
logger.debug("系统用户名为:{}", loginName);
// 查询系统用户
SysUserEntity sysUser = sysLogInfoMapper.querysUserByUserName(loginName);
logger.info("系统用户信息:{}", sysUser);
if (null == sysUser) {
throw new RuntimeException("当前系统用户不存在!");
}
return sysUser;
}else {
throw new RuntimeException("暂未匹配到该用户信息");
}
}
}