springboot整合springsecurity、jwt、redis实现权限控制

1、 添加相关依赖

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
         <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- jwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

2、自定义用户对象UserInfo

@Data
public class UserInfo {
    
    
    private int id;
    //姓名
    private String name;
    private String email;
    //域账号
    private String username;
    //其余属性省略....
    
}

3、自定义JwtAuthenticationToken类继承UsernamePasswordAuthenticationToken

public class JwtAuthenticatioToken extends UsernamePasswordAuthenticationToken {
    
    

    private static final long serialVersionUID = 1L;

    private String token;

    public JwtAuthenticatioToken(Object principal, Object credentials){
    
    
        super(principal, credentials);
    }

    public JwtAuthenticatioToken(Object principal, Object credentials, String token){
    
    
        super(principal, credentials);
        this.token = token;
    }

    public JwtAuthenticatioToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities, String token) {
    
    
        super(principal, credentials, authorities);
        this.token = token;
    }

    public String getToken(){
    
    
        return token;
    }

    public void setToken(String token){
    
    
        this.token = token;
    }

    public static long getSerialVersionUID(){
    
    
        return serialVersionUID;
    }
}

4、自定义JwtUserDetails类继承userdetails包下的User类,其中User类实现了UserDetails

public class JwtUserDetails extends User {
    
    
    private static final long serialVersionUID = 1L;
    private UserInfo userInfo;

    public JwtUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities, UserInfo userInfo) {
    
    
        this(username, password, true,true,true,true,authorities);
        this.userInfo = userInfo;
    }

    public JwtUserDetails(String username, String password, boolean enabled, boolean accountNonExpired,
                          boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
    
    
        super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
    }
    public UserInfo getUserInfo(){
    
    
        return userInfo;
    }

    public void setUserInfo(UserInfo userInfo){
    
    
        this.userInfo = userInfo;
    }
}

5、自定义权限封装类GrantedAuthorityImpl实现GrantedAuthority接口

//权限封装
public class GrantedAuthorityImpl implements GrantedAuthority, Serializable {
    
    

    private static final long serialVersionUID = 1L;

    private String authority;

    public GrantedAuthorityImpl(String authority){
    
    
        this.authority = authority;
    }

    public void setAuthority(String authority){
    
    
        this.authority = authority;
    }

    @Override
    public String getAuthority() {
    
    
        return this.authority;
    }
}

6、自定义bean UserDetailsServiceImpl实现UserDetailsService接口,并且将此bean命名为@Service(“userDetailsService”)

//实现loadUserByUsername方法
@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
    //获取userInfo用户信息
        UserInfo userVo=new UserInfo();
        //资源列表
        List<String> resourceVos = new ArrayList<>();
        //TODO 查询操作,获取资源,用户信息
        
        //生成GrantedAuthority权限列表
        List<GrantedAuthority> grantedAuthorities = resourceVos.stream().map(GrantedAuthorityImpl::new).collect(Collectors.toList());
        BCryptPasswordEncoder s=  new BCryptPasswordEncoder();
        userVo.setPermissionCodes(resourceVos);
        userVo.setUsername(username);
        userVo.setRoleDtoList(externalRoleDtos);
        userVo.setCompanyDtoList(externalCompanyDtoList);
        userVo.setSystemCodes(systemCodes);
        //生成JwtUserDetails对象
        return new JwtUserDetails(username, s.encode("888888"), grantedAuthorities,userVo);
    }

7、自定义JwtAuthenticationProvider类继承DaoAuthenticationProvider作为身份验证提供者

public class JwtAuthenticationProvider extends DaoAuthenticationProvider {
    
    

    public JwtAuthenticationProvider(UserDetailsService userDetailsService) {
    
    
        setUserDetailsService(userDetailsService);
        setPasswordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    
    
        // 可以在此处覆写整个登录认证逻辑

        return super.authenticate(authentication);
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
    
    
        // 可以在此处覆写密码验证逻辑
        super.additionalAuthenticationChecks(userDetails, authentication);
    }

}

8、自定义JwtAuthenticationFilter类继承BasicAuthenticationFilter进行登录认证检查(过滤器)

public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
    
    

    @Autowired
    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
    
    
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException, FilterAuthException {
    
    
        //异常捕获--重定向到controller(filterException)
        try {
    
    
            SecurityUtils.checkAuthentication(request);
        }catch (FilterAuthException e){
    
    
            request.setAttribute("filterException",e);
            request.getRequestDispatcher("/filterException").forward(request,response);
        }
        chain.doFilter(request, response);
    }

}

9、自定义过滤器异常转发controller–FilterExceptionController

@RestController
public class FilterExceptionController {
    
    

    @RequestMapping("/filterException")
    public void filterException(HttpServletRequest request, HttpServletResponse response)throws FilterAuthException{
    
    
        if (request.getAttribute("filterException") instanceof FilterAuthException){
    
    
        	//响应状态码
            response.setStatus(400);
            throw (FilterAuthException) request.getAttribute("filterException");
        }
    }

}

10、配置SecurityConfig

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http
                .cors().and().csrf().disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS,"/**").permitAll()
                .antMatchers("/", "/index.html", "/favicon.ico", "/js/**", "/css/**", "/img/**", "/fonts/**").permitAll()
                .antMatchers("/*.html", "/webjars/**", "/swagger-ui.html/**","/v2/api-docs", "/swagger-resources", "/swagger-resources/**").permitAll()
                .antMatchers("/api/login").permitAll()
                .antMatchers("/login").permitAll()
                .antMatchers("/v1/logout/**").permitAll()
                .antMatchers("/api/**").hasRole("USER") 
                .anyRequest().authenticated();
        //退出登录处理器
        http.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());
        //访问控制时登录状态检查过滤器
        http.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
    }

    //认证器,将自定义用户源放入到spring security配置中
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        // 使用自定义身份验证组件
        UserDetailsService userDetailsService= (UserDetailsService) SpringContextUtils.getBean("userDetailsService");
        auth.authenticationProvider(new JwtAuthenticationProvider(userDetailsService));
    }

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

}

11、定义登录认证类SecurityUtils

public class SecurityUtils {
    
    

    public static JwtAuthenticatioToken login(HttpServletRequest request, String username, String password, AuthenticationManager authenticationManager){
    
    
        JwtAuthenticatioToken token = new JwtAuthenticatioToken(username,password);
        token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        //执行登录认证过程
        Authentication authentication = authenticationManager.authenticate(token);
        //认证成功存储认证信息到上下文
        SecurityContextHolder.getContext().setAuthentication(authentication);
        //生成令牌并返回给客户端
        token.setToken(JwtTokenUtils.generateToken(authentication));
        return token;
    }

    //获取令牌进行认证
    public static void checkAuthentication(HttpServletRequest request) throws FilterAuthException {
    
    
        // 获取令牌并根据令牌获取登录认证信息
        Authentication authentication = JwtTokenUtils.getAuthenticationFromToken(request);
        // 设置登录认证信息到上下文
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    //获取当前用户名
    public static String getUsername(){
    
    
        String username = null;
        Authentication authentication = getAuthentication();
        if (authentication != null){
    
    
            Object principal = authentication.getPrincipal();
            if (principal != null && principal instanceof UserDetails){
    
    
                username = ((UserDetails) principal).getUsername();
            }
        }
        return username;
    }


    //获取用户名
    public static String getUsername(Authentication authentication){
    
    
        String username = null;
        if (authentication != null){
    
    
            Object principal = authentication.getPrincipal();
            if (principal != null && principal instanceof UserDetails){
    
    
                username = ((JwtUserDetails) principal).getUsername();
            }
        }
        return username;
    }

    //获取当前登录信息
    public static Authentication getAuthentication() {
    
    
        if (SecurityContextHolder.getContext() == null){
    
    
            return null;
        }
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return authentication;
    }

    //获取姓名
    public static String getName(){
    
    
        String name = null;
        Authentication authentication = getAuthentication();
        if (authentication != null){
    
    
            Object principal = authentication.getPrincipal();
            if (principal != null && principal instanceof UserDetails){
    
    
                name = ((JwtUserDetails) principal).getUserInfo().getName();
            }
        }
        return name;
    }
}

12、定义jwt工具类JwtTokenUtils

@Component
public class JwtTokenUtils implements Serializable {
    
    
private static final long serialVersionUID = 1L;

    //用户名称
    private static final String USERNAME = Claims.SUBJECT;

    //创建时间
    private static final String CREATED = "created";

    //密钥
    private static final String jwtSECRET = "JWTSecretKey";

    @Value("${mds.reids.base.key}")
    private String mdsReidsBaseKeyTemp;

    @Value("${ccms.redis.base.user.key}")
    private String ccmsReidsBaseUserKeyTemp;

    @Value("${mds.redis.base.auth.project.key}")
    private String mdsRedisBaseAuthProjectKeyTemp;

    private static String mdsReidsBaseKey;

    private static String ccmsRedisBaseUserKey;

    private static String mdsRedisBaseAuthProjectKey;


    @Value("#{'${white.list:}'.empty ? null : '${white.list:}'.split(',')}")
    private  List<String> whiteListTemp;

    private static List<String> whiteList;

    @PostConstruct
    public void init() {
    
    
        mdsReidsBaseKey = mdsReidsBaseKeyTemp;
        ccmsRedisBaseUserKey = ccmsReidsBaseUserKeyTemp;
        mdsRedisBaseAuthProjectKey = mdsRedisBaseAuthProjectKeyTemp;
        whiteList=whiteListTemp;
    }

    //生成令牌
    public static String generateToken(Authentication authentication){
    
    
        Map<String,Object> claims = new HashMap<>(2);
        claims.put(USERNAME,SecurityUtils.getUsername(authentication));
        claims.put(CREATED,new Date());
        return generateToken(claims);
    }

    //从数据声明中生成令牌
    private static String generateToken(Map<String,Object> claims){
    
    
        return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS256,jwtSECRET).compact();
    }

    //根据请求令牌获取登录认证信息
    public static Authentication getAuthenticationFromToken(HttpServletRequest request)throws FilterAuthException{
    
    
        Authentication authentication = null;
        //获取请求携带的令牌
        String token = JwtTokenUtils.getToken(request);
        if (token != null){
    
    
            if(!RedisUtil.hasKey(mdsReidsBaseKey+getUsernameFromToken(token))){
    
    
                throw new FilterAuthException("400","登录信息已过期,请重新登录");
            }
            //判断缓存数据
            if(!judgeRedisValue(token)){
    
    
                //Todo 加白名单,方便开发
                if(getUsernameFromToken(token)!=null &&!whiteList.contains(getUsernameFromToken(token))){
    
    
                    return null;
                }
            }
            //请求令牌不能为空
            if (SecurityUtils.getAuthentication()==null){
    
    
                //上下文中Authentication为空
                Claims claims = getClaimsFromToken(token);
                if (claims == null){
    
    
                    return null;
                }
                String username = claims.getSubject();
                if (username == null){
    
    
                    return null;
                }
                UserInfo userInfo = getUserInfoFromRedis(username);
                if (userInfo == null){
    
    
                    return null;
                }else {
    
    
                    //重新生成authentication对象
                    //权限列表
                    List<GrantedAuthority> authorities = userInfo.getPermissionCodes().parallelStream().map(GrantedAuthorityImpl::new).collect(Collectors.toList());
                    BCryptPasswordEncoder s=  new BCryptPasswordEncoder();
                    UserDetails userDetails = new JwtUserDetails(username, s.encode("888888"), authorities,userInfo);
                    authentication = new JwtAuthenticatioToken(userDetails,null,authorities,token);
                }
            }else {
    
    
                if (validateToken(token,SecurityUtils.getUsername())){
    
    
                    // 如果上下文中Authentication非空,且请求令牌合法,直接返回当前登录认证信息
                    authentication = SecurityUtils.getAuthentication();
                }
            }
        }
        return authentication;
    }

    //获取redis中的用户
    private static UserInfo getUserInfoFromRedis(String username){
    
    
        UserInfo userInfo = new UserInfo();
        String userKey = ccmsRedisBaseUserKey + username;
        if(RedisUtil.hasKey(userKey)){
    
    
            String value = (String) RedisUtil.get(userKey);
            userInfo = JSON.parseObject(value,UserInfo.class);
        }
        return userInfo;
    }

    //验证令牌
    private static boolean validateToken(String token, String username) {
    
    
        String userName = getUsernameFromToken(token);
        if (StringUtils.isEmpty(userName)){
    
    
            return false;
        }
        return (userName.equals(username));
    }

    // 从令牌中获取用户名
    public static String getUsernameFromToken(String token) {
    
    
        String username;
        try {
    
    
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
    
    
            username = null;
        }
        return username;
    }

    //判断令牌是否过期
    public static boolean isTokenExpired(String token) {
    
    
        try {
    
    
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        }catch (Exception e){
    
    
            return false;
        }
    }

    //从令牌中获取数据声明
    private static Claims getClaimsFromToken(String token) {
    
    
        Claims claims;
        try {
    
    
            claims = Jwts.parser().setSigningKey(jwtSECRET).parseClaimsJws(token).getBody();
        }catch (Exception e){
    
    
            claims = null;
        }
        return claims;
    }

    //获取请求token
    public static String getToken(HttpServletRequest request) {
    
    
        String token = request.getHeader("Authorization");
        String tokenHead = "Bearer ";
        if (token == null){
    
    
            token = request.getHeader("token");
        }else if (token.contains(tokenHead)){
    
    
            token = token.substring(tokenHead.length());
        }
        if ("".equals(token)){
    
    
            token = null;
        }
        return token;
    }

    public static boolean judgeRedisValue(String token) throws FilterAuthException {
    
    
        String userName = getUsernameFromToken(token);
        if(!RedisUtil.hasKey(mdsReidsBaseKey+userName)){
    
    
            return false;
        }
        //没有过期且token无效,则是被挤下去(同一账号不允许同时在线)
        if(!token.equals(RedisUtil.get(mdsReidsBaseKey+userName)) && !whiteList.contains(userName)){
    
    
            throw new FilterAuthException("400","您的账号在别处登录,请重新登录");
        }
        //自定义缓存对象--校验通过延长过期时间
        RedisUtil.expire(mdsReidsBaseKey+userName,3600);
        RedisUtil.expire(ccmsRedisBaseUserKey+userName,3600);
        RedisUtil.expire(mdsRedisBaseAuthProjectKey+userName,3600);
        return true;
    }
}

13、登录接口loginController

	@Autowired
    private AuthenticationManager authenticationManager;
 			//用户名密码校验
 			//TODO
 			//系统登录认证
            JwtAuthenticatioToken jwtAuthenticatioToken = SecurityUtils.login(req,username,password,authenticationManager);
            System.out.println("-------登陆成功----------");
            HttpSession hs = req.getSession();
            hs.setMaxInactiveInterval(60);
            String token = jwtAuthenticatioToken.getToken();
            //当前登录信息
            Authentication authentication = SecurityUtils.getAuthentication();
            RedisUtil.set(mdsRedisBaseKey+SecurityUtils.getUsername(authentication),token,3600);
            //保持跟token一致的过期时间
            RedisUtil.set(ccmsRedisBaseUserKey+username,JSON.toJSONString(userVo),3600);
            RedisUtil.hashSet(mdsRedisBaseAuthProjectKey+username,authProjectCodes);
            RedisUtil.expire(mdsRedisBaseAuthProjectKey+username,3600);

14、退出登录时清除上下文信息

		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String username = SecurityUtils.getUsername(authentication);
        LOG.info("Logout user {}", username);
        HttpSession session = request.getSession(false);
        if (session != null) {
    
    
            session.invalidate();
        }
        SecurityContextHolder.clearContext();
        RedisUtil.del(mdsRedisBaseKey+username);
        RedisUtil.del(ccmsRedisBaseUserKey+username);
        RedisUtil.del(mdsRedisBaseAuthProjectKey+username);

15、权限注解@PreAuthorize使用

在需要权限控制的接口上加上注解@PreAuthorize,并且在GrantedAuthorityImpl对象中配置好资源权限
列表,在访问的接口的时候会进行资源权限的校验。
@PreAuthorize("hasAuthority('v1:targetCost:save')")
public Result test(){
    
    
//TODO
return Result.setSuccess(null);
}

16、RedisUtil类

@Component
public class RedisUtil {
    
    

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisUtil.class);

    @Autowired
    private  RedisTemplate<String, Object> redisTemplateTemp;

    private static RedisTemplate<String, Object> redisTemplate;


    @PostConstruct
    public void init() {
    
    
        redisTemplate = redisTemplateTemp;
    }
// =============================common============================

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间()
     * @return
     */
    public static boolean expire(String key, long time) {
    
    
        try {
    
    
            if (time > 0) {
    
    
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
    
    
            LOGGER.error("获取key的失效时间异常" + e.getMessage(), e);
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间() 返回0代表为永久有效
     */
    public static long getExpire(String key) {
    
    
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public static boolean hasKey(String key) {
    
    
        try {
    
    
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
    
    
            LOGGER.error("判断key是否存在异常" + e.getMessage(), e);
            return false;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    public static void del(String... key) {
    
    
        if (key != null && key.length > 0) {
    
    
            if (key.length == 1) {
    
    
                redisTemplate.delete(key[0]);
            } else {
    
    
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    // ============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public static Object get(String key) {
    
    
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public static boolean set(String key, Object value) {
    
    
        try {
    
    
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
    
    
            LOGGER.error("设置String类型异常" + e.getMessage(), e);
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间() time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public static boolean set(String key, Object value, long time) {
    
    
        try {
    
    
            if (time > 0) {
    
    
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
    
    
                set(key, value);
            }
            return true;
        } catch (Exception e) {
    
    
            LOGGER.error("设置String类型及有效期异常" + e.getMessage(), e);
            return false;
        }
    }

    /**
     * hash结构存储
     * @param key   hash的key值
     * @param map   hash的map集合
     * @author hongjx5
     * @return {
    
    {
    
    @link boolean}}
     */
    public static boolean hashSet(String key, Map map){
    
    
        boolean result = false;
        try {
    
    
            redisTemplate.opsForHash().putAll(key,map);
            result = true;
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 获取Map中的value值
     * @param key   redis中的key
     * @param filed     redis存储的map中的key
     * @author hongjx5
     * @return {
    
    {
    
    @link Object}}
     */
    public static Object hashGet(String key,Object filed){
    
    
        if (key == null){
    
    
            return null;
        }
        if (filed == null){
    
    
            return null;
        }
        return redisTemplate.opsForHash().get(key, filed);
    }

    /**
     * 递增
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     * @return
     */
    public static long incr(String key, long delta) {
    
    
        if (delta < 0) {
    
    
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(小于0)
     * @return
     */
    public static long decr(String key, long delta) {
    
    
        if (delta < 0) {
    
    
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_41207479/article/details/118292566