[会写代码的健身爱好者成长史]之spring-security

目录

1.什么是spring-security?

2.准备工作

2.1 添加依赖

 2.2 相关实体类

2.3 sql建表语句

 2.4 相关工具类

2.4.1 Jwt

2.4.2 redis工具类(但是我本人使用还是原生的RedisTemplate)

2.4.3 响应类

3.spring-security 的登录流程

 3.1 根据流程进行代码的编写

3.1.1修改官方默认的账户和密码

3.1.2 自定义登录接口

 3.1.3 定义jwt认证的过滤器

 4.权限

 4.1 测试一下权限


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 中存着了,所以要重新登录一下,在访问

 然后就查询不到了,因为没有权限访问这个接口

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值