springsecurity切换部门、公司等动态配置权限

springsecurity切换部门、公司等动态配置权限

1.需求分析

一个人可以在多个部门任职,需要做一个部门切换按钮并且完成权限的切换

2.难点

一般springsecurity只能在UserDetailsServiceImpl里面的loadUserByUsername里面配置权限,一般只在登录的时候去配置一次,登录完成之后就无法去调用loadUserByUsername重新完成权限授权

3.我的代码流程

登录---->判断用户合法性—>生成token—>用户授权信息存入redis—>返回给前端token

访问权限接口—>jwt拦截器—>校验token合法性—>去redis获取用户授权信息—>将授权信息传入springsecurity

4.关键处理

从代码流程大概可以看出来,我们其实只要更新redis上的数据即可达到权限的切换效果,因为我是从redis上去加载用户的授权信息

5.关键代码
package cn.leadingtv.oauth.service.impl;
import cn.leadingtv.oauth.config.JwtUtil;
import cn.leadingtv.oauth.config.RedisCache;
import cn.leadingtv.oauth.controller.req.OauthIdReq;
import cn.leadingtv.oauth.controller.req.OauthPermissReq;
import cn.leadingtv.oauth.entity.LoginUser;
import cn.leadingtv.oauth.entity.OauthAdmin;
import cn.leadingtv.oauth.entity.OauthPermiss;
import cn.leadingtv.oauth.mapper.OauthPermissMapper;
import cn.leadingtv.oauth.service.LoginService;
import cn.leadingtv.utils.result.Result;
import cn.leadingtv.utils.result.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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.ArrayList;
import java.util.List;
import java.util.Objects;
@Service
@Slf4j
public class LoginServiceImpl implements LoginService {
 
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private RedisCache redisCache;

    @Autowired
    private OauthPermissMapper permissMapper;

    /**
     * 登录
     * @param admin
     * @return
     */
    @Override
    public Result login(OauthAdmin admin) {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(admin.getUsername(),admin.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        if(Objects.isNull(authenticate)){
            throw new RuntimeException("用户名或密码错误");
        }
        //使用userid生成token
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        String userId = loginUser.getOauthAdmin().getId().toString();
        String jwt = JwtUtil.createJWT(userId);
        //authenticate存入redis
        redisCache.setCacheObject("login:"+userId,loginUser);
        //把token响应给前端
        return new Result(ResultCode.SUCCESS.getCode(), "登陆成功").put("token",jwt);
    }
    /**
     * 注销
     * @param req
     * @return
     */
    @Override
    public Result logout() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        Long userid = Long.valueOf(loginUser.getOauthAdmin().getId());
        redisCache.deleteObject("login:"+userid);
        return new Result(ResultCode.SUCCESS.getCode(), "退出成功");
    }

    /**
     * 切换权限
     * @param req
     * @return
     */
    @Override
    public Result changeDepart(OauthIdReq req) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser)authentication.getPrincipal();
        OauthAdmin admin = loginUser.getOauthAdmin();
        log.info("loginUser===>{}", loginUser);
        LoginUser newUser = new LoginUser();
        String id = admin.getId().toString();
        log.info("id===>{}", id);
        newUser.setOauthAdmin(admin);
        OauthPermissReq req1 = new OauthPermissReq();
        req1.setDepartId(req.getId());
        req1.setUserId(admin.getId());
        List<OauthPermiss> permiss = permissMapper.getPermissByDepartId(req1);
        ArrayList<String> list = new ArrayList<>();
        for (OauthPermiss per:permiss) {
            list.add(per.getCode());
        }
        newUser.setPermissions(list);
        redisCache.deleteObject("login:"+id);
        redisCache.setCacheObject("login:"+ id,newUser);
        return Result.status(ResultCode.SUCCESS.getCode(), "权限切换成功");
    }

}
 

package cn.leadingtv.oauth.config;

import cn.leadingtv.oauth.entity.LoginUser;
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
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.hasText(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中
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(request, response);
    }
}

package cn.leadingtv.oauth.service.impl;

import cn.leadingtv.oauth.controller.req.OauthPermissReq;
import cn.leadingtv.oauth.entity.LoginUser;
import cn.leadingtv.oauth.entity.OauthAdmin;
import cn.leadingtv.oauth.entity.OauthPermiss;
import cn.leadingtv.oauth.mapper.OauthAdminMapper;
import cn.leadingtv.oauth.mapper.OauthPermissMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
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.List;
import java.util.Objects;

@Slf4j
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
 
    @Autowired
    private OauthAdminMapper adminMapper;

    @Autowired
    private OauthPermissMapper permissMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名查询用户信息
        LambdaQueryWrapper<OauthAdmin> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(OauthAdmin::getUsername,username);
        OauthAdmin user = adminMapper.selectOne(wrapper);
        //如果查询不到数据就通过抛出异常来给出提示
        if(Objects.isNull(user)){
            throw new RuntimeException("用户名或密码错误");
        }

        //根据用户查询权限信息 添加到LoginUser中
        OauthPermissReq req = new OauthPermissReq();
        req.setUserId(user.getId());
        List<Integer> departIds = permissMapper.getDepartFirst(user.getId());
        log.info("departIds===>{}", departIds);
        req.setDepartId(departIds.get(0));
        List<OauthPermiss> permiss = permissMapper.getPermissByDepartId(req);
        List<String> list = new ArrayList<>();
        for (OauthPermiss per: permiss) {
            list.add(per.getCode());
        }
        //封装成UserDetails对象返回 
        return new LoginUser(user, list);
    }
}

package cn.leadingtv.oauth.entity;

import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

@Data
@NoArgsConstructor
@Slf4j
public class LoginUser implements UserDetails {
 
    private OauthAdmin oauthAdmin;

    //存储权限信息
    private List<String> permissions;

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

    //存储SpringSecurity所需要的权限信息的集合
    @JSONField(serialize = false)
    private List<SimpleGrantedAuthority> authorities;


    @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);
//            authorities.add(authority);
//        }
        authorities = permissions.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        log.info("authorities===>{}", authorities);
        return authorities; //把spring 权限字段集合封装
    }
 
    @Override
    public String getPassword() {
        return oauthAdmin.getPassword();
    }
 
    @Override
    public String getUsername() {
        return oauthAdmin.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;
    }
}

package cn.leadingtv.oauth.config;

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.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;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
                .antMatchers("/user/login").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();

        //把token校验过滤器添加到过滤器链中
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        http.exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint).
                accessDeniedHandler(accessDeniedHandler);

        //允许跨域
        http.cors();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


}

6.总结

就是把redis的 数据给改变,因为每次访问接口的时候都需要去redis读取权限配置

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值