目录
2.4.2 redis工具类(但是我本人使用还是原生的RedisTemplate)
1.什么是spring-security?
spring-security是spring家族推出的一个安全校验权限框架,一般中大型项目都会采用这个框架
2.准备工作
2.1 添加依赖
<!--redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--jwt依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!-- fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
2.2 相关实体类
/**
* 用户表(Users)实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("sys_user")
public class Users implements Serializable {
private static final long serialVersionUID = -40356785423868312L;
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
private String userName;
/**
* 昵称
*/
private String nickName;
/**
* 密码
*/
private String password;
/**
* 账号状态(0正常 1停用)
*/
private String status;
/**
* 邮箱
*/
private String email;
/**
* 手机号
*/
private String phonenumber;
/**
* 用户性别(0男,1女,2未知)
*/
private String sex;
/**
* 头像
*/
private String avatar;
/**
* 用户类型(0管理员,1普通用户)
*/
private String userType;
/**
* 创建人的用户id
*/
private Long createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新人
*/
private Long updateBy;
/**
* 更新时间
*/
private Date updateTime;
/**
* 删除标志(0代表未删除,1代表已删除)
*/
private Integer delFlag;
}
2.3 sql建表语句
CREATE TABLE `sys_user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '用户名',
`nick_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '昵称',
`password` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '密码',
`status` CHAR(1) DEFAULT '0' COMMENT '账号状态(0正常 1停用)',
`email` VARCHAR(64) DEFAULT NULL COMMENT '邮箱',
`phonenumber` VARCHAR(32) DEFAULT NULL COMMENT '手机号',
`sex` CHAR(1) DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',
`avatar` VARCHAR(128) DEFAULT NULL COMMENT '头像',
`user_type` CHAR(1) NOT NULL DEFAULT '1' COMMENT '用户类型(0管理员,1普通用户)',
`create_by` BIGINT(20) DEFAULT NULL COMMENT '创建人的用户id',
`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
`update_by` BIGINT(20) DEFAULT NULL COMMENT '更新人',
`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
`del_flag` INT(11) DEFAULT '0' COMMENT '删除标志(0代表未删除,1代表已删除)',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'
2.4 相关工具类
2.4.1 Jwt
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;
/**
* JWT工具类
*/
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;
}
/**
* 生成jtw
* @param subject token中要存放的数据(json格式)
* @return
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
}
/**
* 生成jtw
* @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=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);
}
/**
* 创建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();
}
public static void main(String[] args) throws Exception {
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjZGNlNjNkNGMyZjQ0MTMzOTEyOGFiYWIyYzhmYThkYyIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTY0NDQ4NjMyMCwiZXhwIjoxNjQ0NDg5OTIwfQ.Mygdi3ufddeStqjJZ42q3snpXOtrEdYFPA-jID3EpAI";
Claims claims = parseJWT(token);
System.out.println(claims.getSubject());
}
/**
* 生成加密后的秘钥 secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
2.4.2 redis工具类(但是我本人使用还是原生的RedisTemplate)
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;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection)
{
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
{
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 删除Hash中的数据
*
* @param key
* @param hkey
*/
public void delCacheMapValue(final String key, final String hkey)
{
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
}
2.4.3 响应类
import com.fasterxml.jackson.annotation.JsonInclude;
/**
* 响应类
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> {
/**
* 状态码
*/
private Integer code;
/**
* 提示信息,如果有错误时,前端可以获取该字段进行提示
*/
private String msg;
/**
* 查询到的结果数据,
*/
private T data;
public ResponseResult(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public ResponseResult(Integer code, T data) {
this.code = code;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public ResponseResult(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}
3.spring-security 的登录流程
登录校验的大致流程:首先前端页面用户输入账户和密码,这边接收到之后去数据库进行匹配校验,如果校验通过,使用用户Id或者某个唯一标识,进行jwt加密生成一个token,将jwt生成的token响应给前端,前端携带这个token去访问其他资源页面的时候,这里会对这token进行jwt解析,看能不能解析出来,如果能解析出来就会得到一个用户Id或者某个唯一标识,
反正是一个能够标识这个用户的信息,这样就可以通过这个用户id去获取用户信息,从而知道这个用户有没有权限去访问这个资源页面,然后在响应给前端
3.1 根据流程进行代码的编写
众所周知spring-security一开始会有一个默认的账户和密码,账户是user,密码是项目启动时在控制台打印的一个随机值,一般情况下是不用官方模式的,要进行修改,那么如何修改呢?
3.1.1修改官方默认的账户和密码
首先要配置一个spring-security配置类,去继承WebSecurityConfigurerAdapter 抽象类
去实现里面 passwordEncoder 这个方法,记得在类上加上 注解 @Configuration
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
一般密码都是进行加密处理,在spring-security 中有一套自己的加密方式,一般情况下需要把加密的密码添加到数据库,如果不想加密,数据库用明文存储,在明文密码前面加 {noop}123
//配置了这个spring-security的默认密码会被顶替,通过BCryptPasswordEncoder专门的加密方式对密码进行加密处理
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
//对密码123进行加密
String encode = bCryptPasswordEncoder.encode("123");
配置完成之后要创建一个类去实现 UserDetailsService 这个接口,因为我们要让spring-security用我们自定义的UserDetailsService,这样UserDetailsService 就可以从数据库里面查询用户名和密码
@Service
public class UserDetailsImpl implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//mybatis-plus,根据userName去数据库查询
LambdaQueryWrapper<Users> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Users::getUserName,username);
Users users = usersMapper.selectOne(queryWrapper);
if (Objects.isNull(users)){
throw new UsernameNotFoundException("用户名或密码错误");
}
//将查询到的信息放到LoginUser这个实体类中
return new LoginUser(users);
}
}
创建一个loginUser去实现UserDetails 接口,重写里面方法。
根据上面的代码逻辑我们这边通过userName去数据库里面查询是否有这个用户,如果能查询到,会将用户信息放到loginUser里面的Users对象里面
public class LoginUser implements UserDetails {
//将users对象放到LoginUser里面
private Users users;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return users.getPassword();
}
@Override
public String getUsername() {
return users.getUserName();
}
//实现默认return的是false,一般情况要改成true
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
先在 spring-security 配置类中重写 authenticationManagerBean 方法,这样才能方便注入
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
3.1.2 自定义登录接口
1.首先要让spring-security 对我们这个登录接口进行放行,这样用户在不登录的情况下也能访问这个自定义的登录接口
2.在接口中我们通过 AuthenticationManager 的 authenticate 方法来进行用户认证,所以需要在SecurityConfig 类中去实现 authenticationManagerBean 方法,并注入到spring 容器里面
3.SecurityConfig 代码实现
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
//配置了这个spring-security的默认密码会被顶替,通过BCryptPasswordEncoder专门的加密方式对密码进行加密处理
// BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
// String encode = bCryptPasswordEncoder.encode("123");
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//对登录接口进行放行的相关配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//因为是前后端分离的项目,不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/user/login").anonymous()
// .permitAll()代表前面的接口请求 "/users/hello" 无论登录没登录都可以访问
.antMatchers("/users/hello").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
}
//在接口中我们通过 AuthenticationManager 的 authenticate 方法来进行用户认证,
//所以需要在SecurityConfig 类中去实现 authenticationManagerBean 方法,并注入到spring 容器里面
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
到LoginService进行业务代码的实现
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisTemplate<String,String> redisTemplate;
// 登录
@Override
public ResponseResult login(Users users) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(users.getUserName(),users.getPassword());
//注入拿到AuthenticationManager对象,调用authenticate方法,获取到用户信息
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
//如果认证不通过,抛出异常
if (Objects.isNull(authenticate)){
throw new UsernameNotFoundException("登录失败");
}
//强转并获取到userId
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getUsers().getId().toString();
//jwt
String jwt = JwtUtil.createJWT(userId);
Map<String, String> map = new HashMap<>();
map.put("token",jwt);
//将用户信息存到redis中,key:userId values: 用户信息
redisTemplate.opsForValue().set(userId, JSON.toJSONString(loginUser));
return new ResponseResult(200,"登录成功",map);
}
3.1.3 定义jwt认证的过滤器
每次有请求过来,会判断有无token,如果没有直接放行去登录,如果有则用jwt解析token获取userId(用户唯一标识),拿到之后根据userId去redis里面查询用户信息,将用户信息放到 SecurityContextHolder ,这样就能判断出这个用户有无权限访问该资源
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取token
String token = request.getHeader("token");
//如果token不存在就放行
if (StringUtils.isBlank(token)){
filterChain.doFilter(request,response);
return;
}
//jwt解析token,获取userId
String userId;
try {
//解析加密的userId
Claims claims = JwtUtil.parseJWT(token);
//获取到userId
userId = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("token is illegal");
}
//根据userId到redis获取用户信息
String loginUser = redisTemplate.opsForValue().get(userId);
LoginUser loginUsers = JSON.parseObject(loginUser, LoginUser.class);
System.out.println(loginUsers+"----------------------------");
if (Objects.isNull(loginUsers)){
throw new RuntimeException("User no login");
}
//存入SecurityContextHolder
//获取权限信息封装到 Authentication
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUsers, null, loginUsers.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request,response);
}
}
写完过滤器,要到 SecurityConfig 里面配置一下
4.权限
springboot集成spring-security开启权限很简单,2个注解即可
首先在spring-security配置类上加注解
然后在需要权限设置的接口上添加注解即可
一般要去数据库查询这个用户的权限字段,如果查询到的权限字段能和接口注解上的 ”test“ 相对于那么这个用户就可以访问这个接口,否则就无权限访问
在 UserDetailsImpl 这个类中查询用户权限的字段
UserDetailsImpl 完整代码如下
@Service
public class UserDetailsImpl implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
@Autowired
private MenuMapper mapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LambdaQueryWrapper<Users> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Users::getUserName,username);
Users users = usersMapper.selectOne(queryWrapper);
if (Objects.isNull(users)){
throw new UsernameNotFoundException("用户名或密码错误");
}
//查询权限信息
List<String> list = mapper.selectPermsByUserId(users.getId());
return new LoginUser(users,list);
}
}
将查询到的权限集合放到LoginUser中,重写 getAuthorities() 方法
LoginUser完整代码如下
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {
private Users users;
//存储权限信息
private List<String> permissions;
//authorities集合不进行序列化
@JSONField(serialize = false)
private List<SimpleGrantedAuthority> authorities;
//将从数据库里面查询到的权限信息permissions集合转化为SimpleGrantedAuthority 类型的list集合
//因为这个重写的方法返回值是GrantedAuthority,SimpleGrantedAuthority继承了GrantedAuthority接口
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//如果这个集合里面有值那么就不需要转换,直接返回就行
if (authorities!=null){
return authorities;
}
//java8 新特性 将String类型的list集合转换为 SimpleGrantedAuthority 类型的list集合
authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
return authorities;
}
@Override
public String getPassword() {
return users.getPassword();
}
@Override
public String getUsername() {
return users.getUserName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public LoginUser(Users users, List<String> permissions) {
this.users = users;
this.permissions = permissions;
}
}
完成了之后还要在过滤器 JwtAuthenticationTokenFilter 中配置一下 ,存入到SecurityContextHolder里
4.1 测试一下权限
随便写一个测试的接口,加上注解,权限就和数据库里面的一致
@GetMapping("/hello")
//hasAuthority其实就是一个方法的调用,这个方法会去判断用户是否有 test 这个权限,如果有这个权限,结果为true,反之亦然
@PreAuthorize("hasAuthority('system:dept:list')")
public String findById(){
return "hello";
}
用postMan测试一下
先登录
然后将token放到上面写的测试接口里面
没问题,如果将数据库里面查询到的权限信息改一下,和上面权限注解里面的不一致试一下
保持,如果这时候再次运行上面的接口地址,是能访问,因为之前已经匹配上了,已经在SecurityContextHolder 中存着了,所以要重新登录一下,在访问
然后就查询不到了,因为没有权限访问这个接口