Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。提供了完善的认证机制和方法级的授权功能。是一款非常优秀的权限管理框架。它的核心是一组过滤器链,不同的功能经由不同的过滤器。这篇文章就是想通过一个小案例将Spring Security整合到SpringBoot中去。要实现的功能就是在认证服务器上登录,然后获取Token,再访问资源服务器中的资源。
对比Shiro框架来说,配置会更复杂一些,但功能更强大,Shrio安全框架上手快,配置简单。
本次项目包含了:redis,mybatis-plus,jwt..
核心过滤器:
SpringSecurity项目依赖application.yml配置4.0.0 org.example SpringSecurity1.0-SNAPSHOT 8 8 org.springframework.boot spring-boot-starter-parent2.6.6 io.jsonwebtoken jjwt0.9.0 redis.clients jedis3.5.2 org.springframework.boot spring-boot-starter-securitylog4j log4j1.2.14 org.springframework.boot spring-boot-dependencies2.1.9.RELEASE pom import com.alibaba fastjsoncom.baomidou mybatis-plus-boot-starter3.3.1.tmp org.springframework.boot spring-boot-starter-webmysql mysql-connector-javaorg.projectlombok lombokprovided org.springframework.boot spring-boot-starter-testorg.junit.jupiter junit-jupiter-enginecom.alibaba fastjson1.2.60 compile org.springframework.boot spring-boot-autoconfigureorg.springframework.boot spring-boot-starter-thymeleafcn.hutool hutool-all5.7.9 org.springframework.boot spring-boot-starter-data-redis2.6.7
server:
port: 8080
#管理数据源
spring:
redis:
host: 127.0.0.1
port: 6379
datasource:
#高版本驱动使用
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/demo?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
#设定用户名和密码
username: root
password: root
#SpringBoot整合Mybatis
mybatis-plus:
#指定别名包
type-aliases-package: com.jt.pojo
#扫描指定路径下的映射文件
mapper-locations: classpath:/mapper
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("sys_user")
public class User implements Serializable {
private static final long serialVersionUID = -40356785423868312L;
@TableId
private Long id;
private String userName;
private String nickName;
private String password;
private String status;
private String email;
private String phonenumber;
private String sex;
private String avatar;
private String userType;
private Long createBy;
private Date createTime;
private Long updateBy;
private Date updateTime;
private Integer delFlag;
}
权限表及pojo类
CREATE TABLE `sys_menu` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `menu_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '菜单名', `path` varchar(200) DEFAULT NULL COMMENT '路由地址', `component` varchar(255) DEFAULT NULL COMMENT '组件路径', `visible` char(1) DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)', `status` char(1) DEFAULT '0' COMMENT '菜单状态(0正常 1停用)', `perms` varchar(100) DEFAULT NULL COMMENT '权限标识', `icon` varchar(100) DEFAULT '#' COMMENT '菜单图标', `create_by` bigint(20) DEFAULT NULL, `create_time` datetime DEFAULT NULL, `update_by` bigint(20) DEFAULT NULL, `update_time` datetime DEFAULT NULL, `del_flag` int(11) DEFAULT '0' COMMENT '是否删除(0未删除 1已删除)', `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='菜单表';
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
@TableName(value="sys_menu")
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Menu implements Serializable {
private static final long serialVersionUID = -54979041104113736L;
@TableId
private Long id;
private String menuName;
private String path;
private String component;
private String visible;
private String status;
private String perms;
private String icon;
private Long createBy;
private Date createTime;
private Long updateBy;
private Date updateTime;
private Integer delFlag;
private String remark;
}
角色表及其他关联表
CREATE TABLE `sys_role` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(128) DEFAULT NULL, `role_key` varchar(100) DEFAULT NULL COMMENT '角色权限字符串', `status` char(1) DEFAULT '0' COMMENT '角色状态(0正常 1停用)', `del_flag` int(1) DEFAULT '0' COMMENT 'del_flag', `create_by` bigint(200) DEFAULT NULL, `create_time` datetime DEFAULT NULL, `update_by` bigint(200) DEFAULT NULL, `update_time` datetime DEFAULT NULL, `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
CREATE TABLE `sys_role_menu` ( `role_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '角色ID', `menu_id` bigint(200) NOT NULL DEFAULT '0' COMMENT '菜单id', PRIMARY KEY (`role_id`,`menu_id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
CREATE TABLE `sys_user_role` ( `user_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '用户id', `role_id` bigint(200) NOT NULL DEFAULT '0' COMMENT '角色id', PRIMARY KEY (`user_id`,`role_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;redis工具类
package com.jt.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
@Autowired
public RedisTemplate redisTemplate;
public void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
public T getCacheObject(final String key)
{
ValueOperations operation = redisTemplate.opsForValue();
return operation.get(key);
}
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}
public long deleteObject(final Collection collection)
{
return redisTemplate.delete(collection);
}
public long setCacheList(final String key, final List dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
public List getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
public BoundSetOperations setCacheSet(final String key, final Set dataSet)
{
BoundSetOperations setOperation = redisTemplate.boundSetOps(key);
Iterator it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
public Set getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
public void setCacheMap(final String key, final Map dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
public Map getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
public void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
public T getCacheMapValue(final String key, final String hKey)
{
HashOperations opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
public void delCacheMapValue(final String key, final String hkey)
{
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}
public List getMultiCacheMapValue(final String key, final Collection
返回前端数据result类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class SysResult implements Serializable {
private Integer status; //200业务执行成功 201业务执行失败
private String msg; //服务器的提示信息
private Object data; //封装后台返回值
public static SysResult fail(){
return new SysResult(201,"业务执行失败",null);
}
public static SysResult success(){
return new SysResult(200,"业务执行成功",null);
}
//服务器返回业务数据
public static SysResult success(Object data){
return new SysResult(200,"业务执行成功",data);
}
public static SysResult success(String msg,Object data){
return new SysResult(200,msg,data);
}
}
一:引入SpringSecurity 的 pom依赖
org.springframework.boot
spring-boot-starter-security
当你引入security的依赖后,再访问接口 就会自动出现一个认证窗口,并且项目控制台会出现一个随机的密码
这就是最初的security的认证。
二:设定成自己数据库账号密码,获取用户信息和权限实际项目中认证的账号密码都必须是从我们自己的数据库中进行查询校验,所有我们要更改
验证的内容
实现UserDetails类这个类是用来封装用户的信息,并在loadUserByUsername方法中进行返回用户信息。
import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;
public LoginUser(User user, List permissions) {
this.user = user;
this.permissions = permissions;
}
private List permissions;
@JSONField(serialize = false) //因为下面的泛型序列化可能会出错,所有用此注解让他不进行序列化
private List authorities;//方便下面调用进行创建静态的
@Override //此方法是获取权限信息的 所以要进行重写
public Collection extends GrantedAuthority> getAuthorities() {
//把permission 中 string 类型的权限封封装成 SimpleGrantedAuthority对象
if (authorities!=null){ //此处进行判断一下,因为每次调用如果不为空则权限已经有
return authorities;
}
authorities = new ArrayList<>();
for (String permission : permissions) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);
authorities.add(authority);
}
return authorities;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUserName();
}
@Override
public boolean isAccountNonExpired() {
return true; //更改成了true
}
@Override
public boolean isAccountNonLocked() {
return true; //更改成了true
}
@Override
public boolean isCredentialsNonExpired() {
return true; //更改成了true
}
@Override
public boolean isEnabled() {
return true; //更改成了true
}
}
实现UserDetailsService类
重写loadUserByUsername方法。这个方法的重写将验证的账号密码都换成了自己数据库的
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jt.mapper.MenuMapper;
import com.jt.mapper.UserMapper;
import com.jt.pojo.LoginUser;
import com.jt.pojo.User;
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.stereotype.Service;
import java.util.List;
import java.util.Objects;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Autowired
private MenuMapper menuMapper;
@Override //设定成自己数据库账号密码 获取用户的信息和权限
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询用户信息
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName,username);
User user = userMapper.selectOne(queryWrapper);
//如果没有查询到用户就抛出异常
if(Objects.isNull(user)){
throw new RuntimeException("用户名或者密码错误");
}
//TODO 获取权限
String s = user.getId().toString();
List list = menuMapper.selectPermsByUserId(s);
//把数据封装成UserDetails返回
return new LoginUser(user,list);
}
}
创建Mapper获取用户信息和权限
创建UsweMapper并继承BaseMapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.jt.pojo.User; import org.apache.ibatis.annotations.Mapper; @Mapper public interface UserMapper extends BaseMapper{ }
创建MenuMapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.jt.pojo.Menu; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface MenuMapper extends BaseMapper
获取权限sql
三:SecurityConfig配置 ·注入BC加密对象
//创建BCryptPasswordEncoder 注入容器 加密方式
@Bean
public PasswordEncoder passwordEncoder(){
//当你将此对象注入容器时,就会自动将密码进行bc的比对校验。
//如果输入的明文密码与数据库中的加密密码不匹配则报错。
//切数据库中必须存储为bc加密的密码
return new BCryptPasswordEncoder();
}
测试BC加密,BC校验
bc是一个强大的APi对象,主要核心包含俩个功能。1.进行加密 2.校验用户输入的密码与数据库中存入的密文是否一致
存入数据库中的密码应该是加密的密码。
@Test
public void test(){
//bc 加密 校验功能
//Security底层加密方式 使用BC加密
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
//1.加密
String encode = bCryptPasswordEncoder.encode("1234");
System.out.println("encode = " + encode); //加密不一样的原因:密码每次加密bc会自动生成一个不同的盐
String encode2 = bCryptPasswordEncoder.encode("1234");
System.out.println("encode2 = " + encode2);
//2.校验用户输入的密码与数据库中存入的密文是否一致
boolean s = bCryptPasswordEncoder.matches("1234", "$2a$10$qvahLUr7Ngf1yNAJmvOUuOhZAFykG4BwiTVkEm2pdXsMgJgW9PVkO");
//如果密码与输入的相同则返回 true
System.out.println("s = " + s);
}
·创建JWT工具类
使用了此工具类进行生成以用户id为主的token。工具类包含了生成JWT密文,及解析JWT密文
多使用与获得token进行解析校验
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
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;
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "sangeng";
public static String getUUID(){
String token = UUID.randomUUID().toString().replaceAll("-", "");
return token;
}
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
}
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=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("sg") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);
}
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
return builder.compact();
}
public static void main(String[] args) throws Exception {
String jwt = createJWT("1234");
System.out.println("jwt加密 = " + jwt);
Claims claims = parseJWT("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIyZTAyOWI3Nzg0Y2I0ZjM5YmYxZDIzNDZhNDRlZTFlNCIsInN1YiI6IjEyMzQiLCJpc3MiOiJzZyIsImlhdCI6MTY1NTY4ODQzMiwiZXhwIjoxNjU1NjkyMDMyfQ.w4gyXVsHhASpWtuUrDLrSAf9trGy3OvLJHuC1-KI8Lo");
System.out.println("解密claims = " + claims.getSubject());
}
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
·创建 JWT认证过滤器
进行校验token
package com.jt.filter;
import com.jt.pojo.LoginUser;
import com.jt.util.JwtUtil;
import com.jt.util.RedisCache;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
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 //token校验过滤器
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取token
String token = request.getHeader("token");
if (StringUtils.isEmpty(token)){//如果为空
//放行 因为后面还有其他的过滤器会进行判断并提示
filterChain.doFilter(request,response);
return;
}
//解析token
String userId;
try {
Claims claims = JwtUtil.parseJWT(token);
userId = claims.getSubject();
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("token非法");
}
//从redis中获取用户信息
String redisKey = "login:"+userId;
LoginUser loginUser = redisCache.getCacheObject(redisKey);//会根据类型自动转换
if (Objects.isNull(loginUser)){
throw new RuntimeException("用户未登录");
}
//存入SecurityContextHolder
//todo 获取权限信息封装到Authentication中 第三个为获取权限loginUser.getAuthorities()
UsernamePasswordAuthenticationToken userToken =
new UsernamePasswordAuthenticationToken(loginUser,null, loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(userToken);
//放行
filterChain.doFilter(request,response);
}
}
创建WebUtils 响应工具类
是为了后面异常处理器进行响应自定义的信息数据使用。
package com.jt.util;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class WebUtils
{
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;
}
}
·创建异常处理器
当出现下面的情况时就会触发,并返回我们自己自定义的处理内容。
自定义认证失败异常处理器
import com.alibaba.fastjson.JSON;
import com.jt.util.WebUtils;
import com.jt.vo.SysResult;
import org.springframework.http.HttpStatus;
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;
@Component //自定义认证失败异常处理器
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
SysResult result = new SysResult().setStatus(401).setMsg("用户认证失败请查询登录");
String json = JSON.toJSONString(result);
//处理异常
WebUtils.renderString(response,json);
}
}
自定义授权失败处理器
import com.alibaba.fastjson.JSON;
import com.jt.util.WebUtils;
import com.jt.vo.SysResult;
import org.springframework.http.HttpStatus;
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;
@Component//授权失败处理器
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
SysResult result = new SysResult().setStatus(403).setMsg("您的权限不足");
String json = JSON.toJSONString(result);
//处理异常
WebUtils.renderString(response,json);
}
}
·创建解决跨域配置
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路径
registry.addMapping("
@PostMapping("/user/login")
public SysResult login(@RequestBody User user){
//AuthenticationManager authenticate 进行用户认证
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());//将用户名账号密码信息封装成authentication对象
Authentication authenticate = authenticationManager.authenticate(authenticationToken);//此处会调用UserDetailServiceImpl中的方法进行账号密码校验
//如果没通过,给出对应提示
if (Objects.isNull(authenticate)){
throw new RuntimeException("登录失败");
}
//如果通过,使用userid生成一个jwt jwt存入返回
LoginUser loginUser = (LoginUser)authenticate.getPrincipal();//Principal中获得了用户的所有数据
Long userId = loginUser.getUser().getId();
String jwt = JwtUtil.createJWT(userId.toString());
HashMap map = new HashMap<>();
map.put("token",jwt);
//把完整的用户信息存入redis中,userid作为key
redisCache.setCacheObject("login:"+userId,loginUser);
//登录成功返回token
return SysResult.success(map);
}
}
退出接口
退出时,只需将存入用户信息的数据都删除即可。
@RequestMapping("/user/logout")
public SysResult logout(){
//获取SecurityContextHolder中的用户id
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();//会获取到用户的信息
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
String userId = loginUser.getUser().getId().toString();
//删除redis中的值
redisCache.deleteObject("login:"+userId);
return SysResult.success("注销成功");
}
权限配置
在用户登录时我们就已经获取到用户的权限信息了,现在要做的就是进行配置权限
1.使用注解的形式
@PreAuthorize("hasAuthority('system:test:list')") //单个
@PreAuthorize("hasAnyAuthority('admin','test','system:test:lisy')") //多个,且只要有一个就可通过
在接口上添加Security提供的权限校验的注解。并调用其中的方法
2.在SecurityConfig中配置路径权限
.antMatchers("/testCors").hasAuthority("system:dept:list222")
3.自定义配置权限方法(功能更强大,可以自己定义权限校验形式条件等等)
import com.jt.pojo.LoginUser;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.List;
@Component("ex")
public class SGExpressionRoot {
public boolean hasAuthority(String authority){
//获取当前用户的权限
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
List permissions = loginUser.getPermissions();
//判断用户权限集合中是否存在authority
return permissions.contains(authority);
}
}
@PreAuthorize("@ex.hasAuthority('system:test:list')")
注解里面再使用注解的形式进行调用自己自定义的权限校验方法。
测试 1.创建HelloController进行测试import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class HelloController {
@Autowired
private UserMapper userMapper;
@RequestMapping("/hello")
@PreAuthorize("hasAuthority('system:test:list22')")
public String hello(){
List users = userMapper.selectList(null);
System.out.println("users = " + users);
return "hello";
}
}
2.使用注解的形式进行测试权限
@PreAuthorize("hasAuthority('system:test:list')")
在接口上进行添加此注解,以及注解中的方法进行设定此接口需要什么权限才能访问
数据库中信息:
使用测试工具进行测试
在登录时会从数据库验证账号密码,并获取用户的权限。且进行了绑定
用户带着token进行访问其他接口:
当权限一样时则通过:



