用户登录权限

说起用户登录,就会涉及到有一个权限问题。因为用户他分普通用户和一些管理员用户之类的。简单的一些注解判断就可以处理好了。

白名单

package com.zzyl.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;

@Configuration
@ConfigurationProperties(prefix = "zzyl.framework.security")
@Data //提供get set 方法
public class SecurityProperties {

    /**
     * 白名单列表 --- 只要用户登录 , 无论有没有分配资源 , 都可以直接访问
     */
    private List<String> publicAccessUrls = new ArrayList<>();

    /**
     * 白名单
     */
    private List<String> ignoreUrl = new ArrayList<>();
}

用户登录

package com.zzyl.service.impl;

import cn.hutool.crypto.digest.BCrypt;
import cn.hutool.json.JSONUtil;
import com.zzyl.constant.CacheConstant;
import com.zzyl.constant.SuperConstant;
import com.zzyl.dto.LoginDto;
import com.zzyl.enums.BasicEnum;
import com.zzyl.exception.BaseException;
import com.zzyl.mapper.ResourceMapper;
import com.zzyl.mapper.UserMapper;
import com.zzyl.properties.JwtTokenManagerProperties;
import com.zzyl.properties.SecurityProperties;
import com.zzyl.service.LoginService;
import com.zzyl.utils.JwtUtil;
import com.zzyl.vo.UserVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class LoginServiceImpl implements LoginService {


    @Autowired
    UserMapper userMapper;

    @Autowired
    JwtTokenManagerProperties jwtTokenManagerProperties;

    @Autowired
    ResourceMapper resourceMapper;  //用于查询用户资源列表

    @Autowired
    SecurityProperties securityProperties; //用户获取白名单列表

    @Autowired
    RedisTemplate<String, String> redisTemplate; //用户存储redis数据


    /**
     * 登录
     * @param loginDto
     * @return
     */
    @Override
    public UserVo login(LoginDto loginDto) {
        //1. 根据用户名查询用户
        UserVo userVo = userMapper.selectByName(loginDto.getUsername());
        //2. 校验用户是否存在 如果不存在则提示用户登录失败
        if (userVo == null) {
            throw new BaseException(BasicEnum.LOGIN_FAIL);
        }
        //3. 校验是否被禁用 如果被禁用 , 则提示账号被禁用 请联系管理员
        if (userVo.getDataState().equals(SuperConstant.DATA_STATE_1)) {
            throw new BaseException(BasicEnum.LOGIN_DISABLE);
        }
        //4. 校验密码是否正确 ,使用BCrypt  如果 不 正确则提示用户或密码错误
        if (!BCrypt.checkpw(loginDto.getPassword(), userVo.getPassword())) {
            throw new BaseException(BasicEnum.LOGIN_ERROR_PASSWORD);
        }
        //5. 密码脱敏
        userVo.setPassword(null);


        //获取用户列表
        List<String> urls = resourceMapper.selectRequestPathByUserId(userVo.getId());
        //获取白名单列表 在配置类里面 properties 包
        List<String> publicAccessUrls = securityProperties.getPublicAccessUrls();
        //合并
        urls.addAll(publicAccessUrls);

        //6. 生成jwt令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put("currentUser", JSONUtil.toJsonStr(userVo));
        String jwt = JwtUtil.createJWT(jwtTokenManagerProperties.getBase64EncodedSecretKey(),
                jwtTokenManagerProperties.getTtlback(),
                claims);
        //6.1 设置令牌
        userVo.setUserToken(jwt);

        //存储Redis 将用户所能访问的 , 所有的在资源列表
        redisTemplate.opsForValue().set(CacheConstant.PUBLIC_ACCESS_URLS + userVo.getId(),
                JSONUtil.toJsonStr(urls),
                jwtTokenManagerProperties.getTtlback(),
                TimeUnit.HOURS);

        //7. 返回数据
        return userVo;
    }
}

登录拦截器

package com.zzyl.intercept;

import cn.hutool.core.text.AntPathMatcher;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.zzyl.constant.CacheConstant;
import com.zzyl.enums.BasicEnum;
import com.zzyl.exception.BaseException;
import com.zzyl.properties.JwtTokenManagerProperties;
import com.zzyl.utils.JwtUtil;
import com.zzyl.utils.UserThreadLocal;
import com.zzyl.vo.UserVo;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

//登录拦截器
@Component
public class UserTokenInterceptor implements HandlerInterceptor {

    @Autowired
    JwtTokenManagerProperties jwtTokenManagerProperties;

    @Autowired
    RedisTemplate<String, String> redisTemplate;  //用户获取redis数据

    //Spring路径匹配器AntPathMatcher
    private AntPathMatcher antPathMatcher = new AntPathMatcher();


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1. 判断是否请求 controller中的方法  ,如果不是 直接放行
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        //2. 从请求头中获取令牌 , Authorization
        String token = request.getHeader("Authorization");
        //3. 校验令牌是否为空 , 为空返回401
        if (StrUtil.isEmpty(token)) {
            //权限不足
            throw new BaseException(BasicEnum.SECURITY_ACCESSDENIED_FAIL);
        }
        //4. 解析令牌,  并校验
        Claims claims = JwtUtil.parseJWT(jwtTokenManagerProperties.getBase64EncodedSecretKey(), token);
        //如果claims为空 , 则抛异常
        if (claims == null) {
            throw new BaseException(BasicEnum.SECURITY_ACCESSDENIED_FAIL);
        }

        //5. 获取用户数据 UserVo  返回object类型
        String jsonStr = String.valueOf(claims.get("currentUser"));
        //5.1 转为 UserVo
        UserVo userVo = JSONUtil.toBean(jsonStr, UserVo.class);

        //6. 取用户id 然后从redis中获取 该用户所能访问的 , 所有资源列表(包含按钮资源列表 和白名单列表)
        //Long userVoId = userVo.getId();
        String urlStr = redisTemplate.opsForValue().get(CacheConstant.PUBLIC_ACCESS_URLS + userVo.getId());
        //6.1 转为集合
        List<String> urls = JSONUtil.toList(urlStr, String.class);

        //7. 校验当前要访问的 资源 是否在用户所能访问的所有资源列表中
        //7.1 获取当前要访问的资源路径  方法加路径
        String target = request.getMethod() + request.getRequestURI();
        //7.2 遍历集合和 当前要访问的资源路径 做比较
        for (String url : urls) {
            if (antPathMatcher.match(url, target)) {
                //9. 如果在 将用户信息存入threadLocal 并放行
                UserThreadLocal.setSubject(jsonStr);
                return true;
            }
        }
        //8. 如果不在 , 则说明没有权限访问该资源 返回403
        throw new BaseException(BasicEnum.SECURITY_ACCESSDENIED_ERROR);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //清除threadLocal中的数据 防止内存泄漏
        UserThreadLocal.removeSubject();
    }
}

把拦截器放在配置里面

package com.zzyl.config;

import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.zzyl.intercept.UserInterceptor;
import com.zzyl.intercept.UserTokenInterceptor;
import com.zzyl.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

/**
 * webMvc高级配置
 */
@Configuration
@ComponentScan("springfox.documentation.swagger.web")
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private UserInterceptor userInterceptor;  //注入小程序拦截器

    @Autowired
    private UserTokenInterceptor userTokenInterceptor;//注入后台拦截器

    @Autowired
    private SecurityProperties securityProperties;  //白名单

    //拦截的时候过滤掉swagger相关路径和登录相关接口
    private static final String[] EXCLUDE_PATH_PATTERNS = new String[]{"/swagger-ui.html",
            "/webjars/**",
            "/swagger-resources",
            "/v2/api-docs",
            // 登录接口
            "/customer/user/login"};

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //小程序拦截器
        registry.addInterceptor(userInterceptor).excludePathPatterns(EXCLUDE_PATH_PATTERNS).addPathPatterns("/customer/**");
        //后台拦截器
        List<String> ignoreUrl = securityProperties.getIgnoreUrl();
        registry.addInterceptor(userTokenInterceptor).excludePathPatterns(ignoreUrl).addPathPatterns("/**");
    }

    /**
     * 资源路径 映射
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //支持webjars
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
        //支持swagger
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        //支持小刀
        registry.addResourceHandler("doc.html")
                .addResourceLocations("classpath:/META-INF/resources/");
    }

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return builder -> {
            // 序列化
            builder.serializerByType(Long.class, ToStringSerializer.instance);
            builder.serializerByType(BigInteger.class, ToStringSerializer.instance);
            builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
            builder.serializerByType(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
            builder.serializerByType(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

            // 反序列化
            builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
            builder.deserializerByType(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
            builder.deserializerByType(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

        };
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值