SpringSecurity从0到1搭建详细教程二——添加权限

SpringSecurity为我们提供了基于注解的权限控制方案,实现限制访问资源所需权限。

首先开启权限,在 SecurityConfig 配置类上添加:

// 开启权限配置
@EnableGlobalMethodSecurity(prePostEnabled = true)

修改UserDetails的实现类LoginUser,让其能封装权限信息

package org.example.bean;

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.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 因为UserDetailsService方法的返回值是UserDetails类型,
 * 所以需要定义一个类,实现该接口,把用户信息封在其中。1
 */
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {

    private User user;
    /**
     * permissions 存放权限集合
     */
    private List<String> permissions;
    /**
     * @JSONField(serialize = false) 不序列化
     * getAuthorities方法的返回值
     */
    @JSONField(serialize = false)
    private List<SimpleGrantedAuthority> authorities;

    public LoginUser(User user, List<String> permissions) {
        this.user = user;
        this.permissions = permissions;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if(authorities!=null){
            return authorities;
        }
        //把permissions中String类型的权限信息封装成它的实现类SimpleGrantedAuthority对象
        /*authorities = new ArrayList<>();
        for (String permission : permissions) {
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);
            newList.add(authority);
        }
        return newList;*/
        // 使用stream流操作
        authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
        return authorities;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.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;
    }
}

UserDetailsServiceImpl 中把权限信息封装到LoginUser中,这里先写死,模拟权限。

package org.example.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.example.bean.LoginUser;
import org.example.bean.User;
import org.example.mapper.UserMapper;
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.ArrayList;
import java.util.Arrays;
import java.util.Objects;

/**
 * 创建一个类实现UserDetailsService接口,加载用户特定数据的核心接口。
 * 重写其中的方法。更加用户名从数据库中查询用户信息
 * 里面定义了一个根据用户名查询用户信息的方法
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        //查询用户信息
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUserName,username);
        User user = userMapper.selectOne(queryWrapper);
        //如果没有查询到用户,就抛出异常
        if(Objects.isNull(user)){
            throw new RuntimeException("用户名或者密码错误");
        }
        //把数据封装成userDetails返回,ArrayList为权限集合,此处作为模拟添加
        return new LoginUser(user,new ArrayList<>(Arrays.asList("test","admin")));
    }
}

在执行请求前,实现了 OncePerRequestFilter 接口的  JwtAuthenticationTokenFilter 过滤器里添加权限集合

package org.example.filter;

import io.jsonwebtoken.Claims;
import org.example.SecurityQuickstartApplication;
import org.example.bean.LoginUser;
import org.example.util.JwtUtil;
import org.example.util.RedisCache;
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;

/**
 * OncePerRequestFilter
 * spirng自带的过滤器,可以保证一次请求只经过一次这个过滤器
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private RedisCache redisCache;

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        // 获取token
        String token = httpServletRequest.getHeader("token");
            // 如果请求头不存在token,则放行并返回
        if(!StringUtils.hasText(token)){
                // 放行后,会去执行后面的filter链,执行完 filter链后,返回到放行这里,会继续往下执行,所以return;
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }
        // 解析token
        String userId;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            // getSubject,此方法获取userId,即数据库表中的主键id
            userId = claims.getSubject();
        } catch (Exception e) {
            throw new RuntimeException("解析token失败!");
        }
        // 从redis获取用户信息
        // 拼接reidsKey,在LoginServiceImpl登录时,将完整的用户信息存入Redis,login:+userId作为key
        String redisKey = "login:"+userId;
        LoginUser loginUser = redisCache.getCacheObject(redisKey);
        if(Objects.isNull(loginUser)){
            throw new RuntimeException("用户未登录!");
        }
        // 存入SecurityContextHolder,将 loginUser.getAuthorities()中的权限集合放到 authenticationToken 中
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }
}

 最后在 controller 中添加 @PreAuthorize("hasAuthority('abc')") 注解,其中 abc、test 为权限名,即在 UserDetailsServiceImpl 默认写死的权限集合中的一个

/**
     * 测试没有abc权限是否可以登录
     * @return
     */
    @GetMapping("/getTest2")
    @PreAuthorize("hasAuthority('abc')")
    public String getTest2(){
        logger.info("测试没有abc权限是否可以登录");
        return "chenggong";
    }
    /**
     * 测试没有test权限是否可以登录
     * @return
     */
    @GetMapping("/getTest3")
    @PreAuthorize("hasAuthority('test')")
    public String getTest3(){
        logger.info("测试有test权限是否可以登录");
        return "chenggong";
    }

测试:getTest2 

getTest3

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值