目录
1、SpringSecurity是什么?
> 首先我们来聊一下什么是SpringSecurity
Spring Security是一个Java框架,用于保护应用程序的安全性。 它提供了一套全面的安全解决方案,包括身份验证、授权、防止攻击等功能。 Spring Security基于过滤器链的概念,可以轻松地集成到任何基于Spring的应用程序中。
2、SpringSecurity能干什么?
1、我们如果在使用语言编写完管理类似的代码时,如若依框架^.^
> 若依框架: 是一个管理系统 可以进行对用户 -- 菜单 -- 部门 ..... 的管理
2、我们想创建一个后端的管理项目有用户管理 和菜单管理
> 不登录的用户不允许访问用户管理和菜单管理
> 用户管理我只想让登录人是超级管理员的可以进行增删改,而不是的则不允许访问
> 菜单管理的话则是让只有菜单管理员才可以进行访问普通管理员访问不了菜单
> 这样的操作SpringSecurity提供了高可用的功能如:身份验证和授权
3、为什么要使用SpringSecurity
如上面的操作 我们不Spring则需要使用拦截器一个个的拦截请求进行实现如
> 我想让没有登录的用户不允许访问某个页面则需要@Filter("*")进行拦截某个指定的请求进行操作判断用户登录了进行放行
> 如果用户登录了还需要去判断权限是否足够去进行是否放行
> 最重要是每个请求都需要写一个拦截!
4、idea SpringSecurity代码集成
pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi_backend</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>ruoyi_backend</name>
<description>ruoyi_backend</description>
<properties>
<spring.web>2.7.2</spring.web>
<java.version>11</java.version>
<fastjson>1.2.60</fastjson>
<mybatis-plus>3.5.2</mybatis-plus>
<mysql>5.1.41</mysql>
<mybatis-spring>2.0.7</mybatis-spring>
<jsoup>1.15.3</jsoup>
</properties>
<dependencies>
<!-- 阿里云 oss-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
<!-- jaxb-api -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- activation -->
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>
<!-- 阿里云OSS-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- web spring-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.web}</version>
</dependency>
<!-- 爬虫 -->
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>${jsoup}</version>
</dependency>
<!-- 快捷生成实体类 -->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<!-- json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson}</version>
</dependency>
<!-- spring-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- mybatis-plus-->
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus}</version>
</dependency>
<!-- thymeleaf模板-->
<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity5 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql}</version>
</dependency>
<!-- junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<!-- mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring}</version>
</dependency>
<!-- devtools热部署依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<!-- 防止将依赖传递到其他模块中 -->
<optional>true</optional>
<!-- 只在运行时起作用,打包时不打进去(防止线上执行打包后的程序,启动文件监听线程File Watcher,耗费大量的内存资源) -->
<scope>runtime</scope>
</dependency>
<!-- 接口文档-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version> <!-- 请确保所有相关依赖都使用相同的版本号 -->
</dependency>
<!-- redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.28</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.28</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
SecurityConfig:
package com.ruoyi.security;
import com.ruoyi.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @Description //TODO
* @Date 2023/10/24 15:26
* @Author yangshy
**/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired //认证终端 处理认证失败异常
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired//处理访问失败异常
private AccessDeniedHandler accessDeniedHandler;
//创建BCryptPasswordEncoder注入容器
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
csrf().
disable().
sessionManagement().
sessionCreationPolicy(SessionCreationPolicy.STATELESS).
and().
authorizeRequests().
antMatchers("/user/login").
anonymous().
anyRequest().
authenticated();
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
//配置 异常处理器 --- 认证失败处理器
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
//配置 异常处理器 ---授权失败处理器
.exceptionHandling().accessDeniedHandler(accessDeniedHandler).and();
http.cors();
}
}
JwtAuthenticationTokenFilter:
package com.ruoyi.filter;
import com.alibaba.fastjson.JSON;
import com.ruoyi.config.Apiconfig.LoginUser;
import com.ruoyi.mapper.SysUserMapper;
import com.ruoyi.util.WebTokenUtils;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private SysUserMapper sysUserMapper;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 获取token
String token = request.getHeader("Authorization");
if(!StringUtils.hasText(token)){
filterChain.doFilter(request,response);
return;
}
// 解析token
String userId=null;
try {
Claims claims = WebTokenUtils.parseJwt(token);
userId = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("token非法");
}
// 从redis中获取用户信息
String redisKey="login:"+userId;
// 类型的转换
String user = (String) redisTemplate.opsForValue().get(redisKey);
LoginUser loginUser = JSON.parseObject(user, LoginUser.class);
if(Objects.isNull(user)){
throw new RuntimeException("用户未登录");
}
// 存入SecurityContextHolder
//TODO 获取权限信息封装到Authentication中
// 查询对应的权限
UsernamePasswordAuthenticationToken authenticationToken=
new UsernamePasswordAuthenticationToken
(loginUser,null,loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request,response);
}
}
SysLoginController:
package com.ruoyi.controller;
import com.ruoyi.config.ResultConfig.Result;
import com.ruoyi.entity.bo.SysUserBo;
import com.ruoyi.service.SysLoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("user")
@CrossOrigin
public class SysLoginController {
@Autowired
private SysLoginService sysloginService;
@RequestMapping(value = "login", method = RequestMethod.POST)
Result login(@RequestBody SysUserBo sysUserBo) {
return sysloginService.login(sysUserBo);
}
@RequestMapping("signOut")
Result signOut() {
return sysloginService.signOut();
}
}
SysLoginServiceImpl:
package com.ruoyi.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.config.Apiconfig.LoginUser;
import com.ruoyi.config.ResultConfig.Result;
import com.ruoyi.entity.bo.SysUserBo;
import com.ruoyi.entity.po.SysUser;
import com.ruoyi.mapper.SysUserMapper;
import com.ruoyi.service.SysLoginService;
import com.ruoyi.util.WebTokenUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Service
public class SysLoginServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysLoginService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisTemplate redisTemplate;
@Override
public Result login(SysUserBo sysUserBo) {
// 进行认证
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(sysUserBo.getUsername(), sysUserBo.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if (Objects.isNull(authenticate)) {
throw new RuntimeException("认证失败用户信息不存在");
}
LoginUser principal = (LoginUser) authenticate.getPrincipal();
System.out.println(principal);
Long userId = principal.getSysUser().getUserId();
String jwt = WebTokenUtils.createJWT(userId.toString());
Map<String, String> map = new HashMap<>();
map.put("token", jwt);
redisTemplate.opsForValue().set("login:" + userId, JSON.toJSONString(principal));
return new Result().OK(map);
}
/**
* @description:退出登录
* @author: Liangpengbo
* @date: 20:27
* @param: []
* @return: com.example.springbootsecurity.ResultConfig.Result
**/
@Override
public Result signOut() {
// 获取SecurityContextHolder中
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
LoginUser principal = (LoginUser) authentication.getPrincipal();
Long userId = principal.getSysUser().getUserId();
Boolean delete = redisTemplate.delete("login:" + userId.toString());
return Result.OK("注销成功");
}
}
UserDetailsServiceImpl:
package com.ruoyi.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.config.Apiconfig.LoginUser;
import com.ruoyi.entity.po.SysUser;
import com.ruoyi.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser sysUser = sysUserMapper.selectOne(Wrappers.lambdaQuery(SysUser.class).eq(SysUser::getUserName, username));
if (Objects.isNull(sysUser)) {
throw new RuntimeException("用户名密码错误");
}
// 查询对应的权限信息
List<String> str = sysUserMapper.selectPermission(sysUser.getUserId());
return new LoginUser(sysUser,str);
}
}
AccessDeniedHandlerImpl:
package com.ruoyi.controller.exception;
import com.alibaba.fastjson.JSON;
import com.ruoyi.config.ResultConfig.Result;
import com.ruoyi.util.WebUtil;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
// * @Description //处理无权限异常
// * @Date 2023/10/24 15:45
// * @Author yangshy
// **/
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override//全局异常处理
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
WebUtil.renderString(response, JSON.toJSONString(Result.NO_PERMISSIONS("当前用户无访问权限")));
}
}
AuthenticationEntryPointImpl:
package com.ruoyi.controller.exception;
import com.alibaba.fastjson.JSON;
import com.ruoyi.config.ResultConfig.Result;
import com.ruoyi.util.WebUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Description //处理认证失败异常
* @Date 2023/10/24 15:53
* @Author yangshy
**/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
WebUtil.renderString(response, JSON.toJSONString(Result.NO_LOGIN("用户名或者密码错误")));
}
}
Result:
package com.ruoyi.config.ResultConfig;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T>{
private Integer code;
private T data;
private String msg;
/**
* @description: 访问请求成功
* @author: Liangpengbo
* @date: 18:57
* @param: [t]
* @return: void
**/
public static Result OK(Object t){
Result<Object> obj = new Result<>();
obj.setData(t);
obj.setCode(ResultTest.OK.code);
obj.setMsg(ResultTest.OK.msg);
return obj;
}
/**
* @description: 访问请求失败
* @author: Liangpengbo
* @date: 18:57
* @param: [t]
* @return: void
**/
public static Result ERROR(Object t){
Result<Object> obj = new Result<>();
obj.setData(t);
obj.setCode(ResultTest.ERROR.code);
obj.setMsg(ResultTest.ERROR.msg);
return obj;
}
public static Result NO_USERNAME(Object o) {
Result<Object> obj = new Result<>();
obj.setData(o);
obj.setCode(ResultTest.NO_USERNAME.code);
obj.setMsg(ResultTest.NO_USERNAME.msg);
return obj;
}
public static Result NO_PASSWORD(Object o) {
Result<Object> obj = new Result<>();
obj.setData(o);
obj.setCode(ResultTest.NO_PASSWORD.code);
obj.setMsg(ResultTest.NO_PASSWORD.msg);
return obj;
}
public static Result capacity(Object o) {
Result<Object> obj = new Result<>();
obj.setData(o);
obj.setCode(ResultTest.ACCOMMODATE_EXCEED.code);
obj.setMsg(ResultTest.ACCOMMODATE_EXCEED.msg);
return obj;
}
public static Result lessThanCurrent(Object o) {
Result<Object> obj = new Result<>();
obj.setData(o);
obj.setCode(ResultTest.LESSTHANANERROR.code);
obj.setMsg(ResultTest.LESSTHANANERROR.msg);
return obj;
}
public static Result inTheInterval(Object o) {
Result<Object> obj = new Result<>();
obj.setData(o);
obj.setCode(ResultTest.INTERVAL.code);
obj.setMsg(ResultTest.INTERVAL.msg);
return obj;
}
public static Result NO_NOAPPPINTMENTS(Object o) {
Result<Object> obj = new Result<>();
obj.setData(o);
obj.setCode(ResultTest.NO_NOAPPOINTMENTS.code);
obj.setMsg(ResultTest.NO_NOAPPOINTMENTS.msg);
return obj;
}
public static Result NO_USER(Object o) {
Result<Object> obj = new Result<>();
obj.setData(o);
obj.setCode(ResultTest.NO_USER.code);
obj.setMsg(ResultTest.NO_USER.msg);
return obj;
}
public static Result USERPERMISSIONS(Object o) {
Result<Object> obj = new Result<>();
obj.setData(o);
obj.setCode(ResultTest.USER_PERMISSIONS.code);
obj.setMsg(ResultTest.USER_PERMISSIONS.msg);
return obj;
}
public static Object NO_LOGIN(Object o) {
Result<Object> obj = new Result<>();
obj.setData(o);
obj.setCode(ResultTest.NO_LOGIN.code);
obj.setMsg(ResultTest.NO_LOGIN.msg);
return obj;
}
public static Object NO_PERMISSIONS(Object o) {
Result<Object> obj = new Result<>();
obj.setData(o);
obj.setCode(ResultTest.NO_PERMISSIONS.code);
obj.setMsg(ResultTest.NO_PERMISSIONS.msg);
return obj;
}
}
WebTokenUtils:
package com.ruoyi.util;//package com.util;
import io.jsonwebtoken.*;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
/**
* @Description //TODO
* @Date 2023/10/24 16:27
* @Author yangshy
**/
public class WebTokenUtils {
// 有效期为
public static final Long JWT_TTL = 1000L * 60 * 60 ;// 60 * 60 * 1000 一个小时
// 设置秘钥明文
public static final String JWT_KEY = "security";
public static String getUUID(){
String token = UUID.randomUUID().toString().replace("-", "");
return token;
}
/**
* 解析token 获取Claims
*
* @param token jwt生成的token
* @return Claims
*/
public static Claims parseJwt(String token) {
return Jwts.parser().setSigningKey(JWT_KEY).parseClaimsJws(token).getBody();
}
/**
* 生成jwt
*/
public static String createJWT(String subject){
JwtBuilder builder = getJwtBuilder(subject,null,getUUID());//设置过期时间
return builder.compact();
}
/**
* 生成jwt
* @param subject token中要存放的数据(json格式)
* @param ttlMillis token超时时间
* @return
*/
public static String createJWT(String subject, Long ttlMillis){
JwtBuilder builder = getJwtBuilder(subject,ttlMillis,getUUID());//设置过期时间
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject,Long ttlMillis,String uuid){
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis == null){
ttlMillis = JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid)//唯一的id
.setSubject(subject)//主题 可以是json数据
.setIssuer("hjy")//签发者
.setIssuedAt(now)//签发时间
.signWith(signatureAlgorithm,secretKey)//使用HS256对称加密算法签名,第二个参数为秘钥
.setExpiration(expDate);
}
/**
* 创建token
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id,String subject,Long ttlMillis){
JwtBuilder builder = getJwtBuilder(subject,ttlMillis,id);//设置过期时间
return builder.compact();
}
/**
* 生成加密后的秘钥 secretKey
* AES 双向加密
* @return
*/
public static SecretKey generalKey(){
byte[] encodedKey = Base64.getDecoder().decode(JWT_KEY);
SecretKeySpec key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
}
WebUtil:
package com.ruoyi.util;//package com.util;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Description //TODO
* @Date 2023/10/24 15:47
* @Author yangshy
**/
public class WebUtil {
/**
* 将字符串渲染到客户端
* @param response 渲染对象
* @param string 待渲染的字符串
* @return null
*/
public static String renderString(HttpServletResponse response, String string) {
try
{
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
}
catch (IOException e)
{
e.printStackTrace();
}
return null;
}
}
SysUserMapper:
package com.ruoyi.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.entity.bio.CheckTheClass;
import com.ruoyi.entity.bio.EchoSys;
import com.ruoyi.entity.bo.SysuserBoS;
import com.ruoyi.entity.po.SysUser;
import com.ruoyi.entity.vo.SysuserVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
List<String> selectPermission(Long userId);
}
SysUser:
package com.ruoyi.entity.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SysUser implements Serializable{
private static final long serialVersionUID = -2738061495986300039L;
@TableId(type = IdType.AUTO)
private Long userId; // 用户ID
private Long deptId; // 部门ID
private String userName; // 用户账号
private String nickName; // 用户昵称
private String userType; // 用户类型(00系统用户)
private String email; // 用户邮箱
private String phonenumber; // 手机号码
private String sex; // 用户性别(0男 1女 2未知)
private String avatar; // 头像地址
private String password; // 密码
private String status; // 帐号状态(0正常 1停用)
private String delFlag; // 删除标志(0代表存在 2代表删除)
private String loginIp; // 最后登录IP
private Date loginDate; // 最后登录时间
private String createBy; // 创建者
private Date createTime; // 创建时间
private String updateBy; // 更新者
private Date updateTime; // 更新时间
private String remark; // 备注
}
SysUserMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.mapper.SysUserMapper">
<select id="selectPermission" resultType="java.lang.String">
select perms from sys_user_role se inner join sys_role_menu su on se.role_id=su.role_id inner join sys_menu sn on sn.menu_id=su.menu_id where se.user_id=#{id}
</select>
</mapper>
5、代码的讲解
登录:
前端我们使用的是vue的项目
简洁:
<form @submit.prevent="login"> <div class="form-group"> <label for="username">Username:</label> <input type="text" id="username" v-model="user.username" required > </div> <div class="form-group"> <label for="password">Password:</label> <input type="password" id="password" v-model="user.password" required > </div> <button type="submit">Login</button> export default { name: "loginDemo", data() { return { user: { username: "", password: "", }, }; }, methods: { login() { this.$http.post("user/login",this.user).then((result) => { if(result.data.code==200){ this.$message({ message:"恭喜您登录成功", type:'success' }) setToken(result.data.data.token) this.$router.push("/") }else{ this.$message.error("登录失败"+result.data.msg) } }) }, }, }; </script>
当我们点击Login的按钮的时候就会让form触发事件去执行login的方法执行
> 使用axios去向后端发送异步请求进行登录
> 后端访问路径代码
@RequestMapping(value = "login", method = RequestMethod.POST) Result login(@RequestBody SysUserBo sysUserBo) { return sysloginService.login(sysUserBo); }
1、使用@RequestBody
> 所以需要发送JSON格式的请求进行解析编译
> axios默认发送的是json格式的请求
2、因为我们在此之前会被拦截器拦截所以我们先回去执行拦截器的方法
JwtAuthenticationTokenFilter:
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 获取token String token = request.getHeader("Authorization"); if(!StringUtils.hasText(token)){ filterChain.doFilter(request,response); return; } // 解析token String userId=null; try { Claims claims = WebTokenUtils.parseJwt(token); userId = claims.getSubject(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("token非法"); } // 从redis中获取用户信息 String redisKey="login:"+userId; // 类型的转换 String user = (String) redisTemplate.opsForValue().get(redisKey); LoginUser loginUser = JSON.parseObject(user, LoginUser.class); if(Objects.isNull(user)){ throw new RuntimeException("用户未登录"); } // 存入SecurityContextHolder //TODO 获取权限信息封装到Authentication中 // 查询对应的权限 UsernamePasswordAuthenticationToken authenticationToken= new UsernamePasswordAuthenticationToken (loginUser,null,loginUser.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); filterChain.doFilter(request,response); }
1、 进来会获取一遍token 判断是否存在这个token : token是从浏览器带过来的
> 为什么获取token并确定?
因为这是判断用户是否登录的方式,后面会讲到为什么使用这个方式
2、确定完用户是否登录成功后如果用户登录不成功
> 如果登录不成功则去放行让他去可以访问login路径登录
>
@RequestMapping(value = "login", method = RequestMethod.POST) Result login(@RequestBody SysUserBo sysUserBo) { return sysloginService.login(sysUserBo); }
进入之后 根据方法体执行之后进入login方法
@Override public Result login(SysUserBo sysUserBo) { // 进行认证 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(sysUserBo.getUsername(), sysUserBo.getPassword()); Authentication authenticate = authenticationManager.authenticate(authenticationToken); if (Objects.isNull(authenticate)) { throw new RuntimeException("认证失败用户信息不存在"); } LoginUser principal = (LoginUser) authenticate.getPrincipal(); System.out.println(principal); Long userId = principal.getSysUser().getUserId(); String jwt = WebTokenUtils.createJWT(userId.toString()); Map<String, String> map = new HashMap<>(); map.put("token", jwt); redisTemplate.opsForValue().set("login:" + userId, JSON.toJSONString(principal)); return new Result().OK(map); }
进去之后通过
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(sysUserBo.getUsername(), sysUserBo.getPassword());
的传参就可以调用
UserDetailsServiceImpl里面的
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser sysUser = sysUserMapper.selectOne(Wrappers.lambdaQuery(SysUser.class).eq(SysUser::getUserName, username)); if (Objects.isNull(sysUser)) { throw new RuntimeException("用户名密码错误"); } // 查询对应的权限信息 List<String> str = sysUserMapper.selectPermission(sysUser.getUserId()); return new LoginUser(sysUser,str); }
loadUserByUsername的方法
> 首先SysUserMapper.selectOne() --- > 查询出数据库是否有这个用户 如果返回null则就提示用户账号密码错误
> 如果有这个数据则去查询对应的用户权限(这里我们需要一张用户表和一张权限表)
> 查询出用户权限后封装一个对象用于后期的调用
这里return了之后就返回到了之前的方法体
if (Objects.isNull(authenticate)) { throw new RuntimeException("认证失败用户信息不存在"); } LoginUser principal = (LoginUser) authenticate.getPrincipal(); System.out.println(principal); Long userId = principal.getSysUser().getUserId(); String jwt = WebTokenUtils.createJWT(userId.toString()); Map<String, String> map = new HashMap<>(); map.put("token", jwt); redisTemplate.opsForValue().set("login:" + userId, JSON.toJSONString(principal)); return new Result().OK(map);
> 通过authenticate 来判断是否返回值 如果没有返回值证明用户没有登录 如果有返回值证明用户已经登录并且查询权限了
然后可以通过 authenticate.getPrincipal()调用我们刚才封装好的对象
LoginUser principal = (LoginUser) authenticate.getPrincipal();
然后可以通过principal.getSysuser().getUserId()可以进行查询出用户的id
Long userId = principal.getSysUser().getUserId();
然后可以通过WebTokenUtils.createJWT(userId.toString())去生成一个token使用用户的id作为登录的标识
String jwt = WebTokenUtils.createJWT(userId.toString());
然后可以通过redis存储用户的信息
redisTemplate.opsForValue().set("login:" + userId, JSON.toJSONString(principal));
然后把生成map存储用户的信息进行返回给前端
Map<String, String> map = new HashMap<>(); map.put("token", jwt); return Result.OK(map)
下一次的时候前端可以进行存储token 然后访问后端时候就可以对其进行下一步授权的操作
JwtAuthenticationTokenFilter:@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 获取token String token = request.getHeader("Authorization"); if(!StringUtils.hasText(token)){ filterChain.doFilter(request,response); return; } // 解析token String userId=null; try { Claims claims = WebTokenUtils.parseJwt(token); userId = claims.getSubject(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("token非法"); } // 从redis中获取用户信息 String redisKey="login:"+userId; // 类型的转换 String user = (String) redisTemplate.opsForValue().get(redisKey); LoginUser loginUser = JSON.parseObject(user, LoginUser.class); if(Objects.isNull(user)){ throw new RuntimeException("用户未登录"); } // 存入SecurityContextHolder //TODO 获取权限信息封装到Authentication中 // 查询对应的权限 UsernamePasswordAuthenticationToken authenticationToken= new UsernamePasswordAuthenticationToken (loginUser,null,loginUser.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); filterChain.doFilter(request,response); }
由于用户已经有token了所以去进行判断
String userId=null; try { Claims claims = WebTokenUtils.parseJwt(token); userId = claims.getSubject(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("token非法"); } // 从redis中获取用户信息 String redisKey="login:"+userId; // 类型的转换 String user = (String) redisTemplate.opsForValue().get(redisKey); LoginUser loginUser = JSON.parseObject(user, LoginUser.class); if(Objects.isNull(user)){ throw new RuntimeException("用户未登录"); } // 存入SecurityContextHolder //TODO 获取权限信息封装到Authentication中 // 查询对应的权限 UsernamePasswordAuthenticationToken authenticationToken= new UsernamePasswordAuthenticationToken (loginUser,null,loginUser.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); filterChain.doFilter(request,response);
首先先定义一个userId为null 方便后面接收id
我们登录的时候已经把token是通过id进行存储的所以我们可以把userId进行转换成数字
转换了之后就可以从redi通过id进行获取数据然后进行授权的操作
// 查询对应的权限 UsernamePasswordAuthenticationToken authenticationToken= new UsernamePasswordAuthenticationToken (loginUser,null,loginUser.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); filterChain.doFilter(request,response);
new UsernamePasswordAuthenticationToken() 第一个参数是用户的登录信息 第二个参数是null 第三个参数是用户的权限信息
> 比如用户权限登录时候查询出来的是 sys/user/list
> 我们后面要访问deleteBulk的路径
@PostMapping("deleteInBulk") @ApiOperation("批量删除") @PreAuthorize("hasAuthority('system:user:del')") Result deleteInBulk(@RequestBody ldsRequest idsRequest) { try { Integer integer = sysuserService.deleteInBulk(idsRequest); return Result.OK("删除成功了哦~"); } catch (Exception e) { return Result.ERROR("后台接口连接超时"); } }
资源绑定了后查询出来的权限有@PreAuthorize("hasAuthority('system:user:del')")这个权限的用户就可以访问这个路径,如果没有则不可以访问!
完结散花❀