SpringBoot纯后台,ThreadLocal实现token登录验证,非HttpServletRequest参数获取验证信息,设备类型获取。

2,275 阅读4分钟

1.获取请求设备进行注册

1.1 mobile的依赖添加。其他swagger以及springboot基本依赖请自行添加。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mobile</artifactId>
            <version>1.5.22.RELEASE</version>
        </dependency>

1.2先将获取请求设备的Bean注入到mvc之中。

@Component
public class MvcConf implements WebMvcConfigurer {

    @Bean
    public DeviceHandlerMethodArgumentResolver
    deviceHandlerMethodArgumentResolver() {
        return new DeviceHandlerMethodArgumentResolver();
    }

    @Override
    public void addArgumentResolvers(
            List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(deviceHandlerMethodArgumentResolver());
    }
}

2.手动实现拦截器

2.1创建用户实体,设计的数据表中并无Device相关字段,所以实体使用时,为了方便,直接通过@Transient注解添加到以映射的实体类中。

@Table(name="sys_user")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SysUser implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 编号
     */
    @Id
    private String id;

    /**
     * 归属公司
     */
    private String companyId;

    /**
     * 归属部门
     */
    private String officeId;

    /**
     * 登录名
     */
    private String loginName;

    /**
     * 密码
     */
    private String password;

    /**
     * 可逆密码
     */
    private String password2;

    /**
     * 姓名
     */
    private String name;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 电话
     */
    private String phone;

    /**
     * 手机
     */
    private String mobile;

    /**
     * 用户类型
     */
    private String userType;

    /**
     * 是否可登录
     */
    private String loginFlag;

    /**
     * 删除标记
     */
    private String delFlag;

    /**
     * 安全密匙
     */
    private String securityKey;

    /**
     * 判断是pc、手机、pad
     */
    @Transient
    private Device device;

2.2UserAccountHolder对ThreadLocal相关的操作

就是相关本地线程的获取与使用。

public class UserAccountHolder {

    private UserAccountHolder() {
    }

    private static final ThreadLocal<SysUser> threadLocal = new ThreadLocal<>();

    public static SysUser getUser() {
        return threadLocal.get();
    }

    public static void setUser(final SysUser user) {
        threadLocal.set(user);
    }

    public static void clear() {
        threadLocal.remove();
    }
}

2.3token的枚举。

public enum HttpHeaderKeyEnums {

    /**
     * 用户token
     */
    TOKEN("token","登录用Token");

    private String code;
    private String desc;

    HttpHeaderKeyEnums(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public String getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}

2.3继承OncePerRequestFilter这个拦截器,实现token的获取与校验,设备类型的获取。

其中的AuthCacheService就是一个缓存的的获取,我这用的ehcache实现实现的,有多个配置实现类,比较繁琐暂时就不粘出来了。我本人其实更倾向于用redies,你们可以试试。

public class AuthFilter extends OncePerRequestFilter {

    private PathMatcher ignoreUrlMatcher = new AntPathMatcher();

    @Autowired
    private AuthCacheService authCacheService;

    private String[] ignoreURLs = {
            "/",
            "/error",
            "/auth/login",
            "/webjars/**",
            "/swagger-resources/**",
            "/v2/**",
            "/swagger-ui.html",
            "/socket/**",
            "/integration/**",
            "/desktop.html/**",
            "/static/**",
            "/simulator/**"
    };


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            if (request.getMethod().equalsIgnoreCase(HttpMethod.OPTIONS.name())) {
                return;
            }
            String token = request.getHeader(HttpHeaderKeyEnums.TOKEN.getCode()) == null ? request.getParameter(HttpHeaderKeyEnums.TOKEN.getCode()) : request.getHeader(HttpHeaderKeyEnums.TOKEN.getCode());
            if (StringUtils.isBlank(token)) {
                this.noLogin(response);
                return;
            }
            // 登录后存入缓存的用户信息,跟这次请求的一个对比
            if (!authCacheService.containsLoginToken(token)) {
                /**
                 * 未登录
                 */
                this.noLogin(response);
                return;
            }
            /**
             * 已登录
             */
             // 验证成功后,获取用户全部信息
            SysUser user = authCacheService.getLoginToken(token);
            // 获取设备信息放入实体中
            user.setDevice(DeviceUtils.getCurrentDevice(request));
            // 将完整的用户实体添加的ThreadLocal中
            UserAccountHolder.setUser(user);
            // 执行
            filterChain.doFilter(request, response);
        } finally {
            UserAccountHolder.clear();
        }
    }

    @SneakyThrows
    private void noLogin(HttpServletResponse response) {
        response.setHeader(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
        response.sendError(HttpStatus.FORBIDDEN.value(), "未登录");
    }

    // 路径放行
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        String requestPath = getRequestPath(request);
        for (String url : ignoreURLs) {
            if (ignoreUrlMatcher.match(url, requestPath)) {
                return true;
            }
        }
        return false;
    }

    private String getRequestPath(HttpServletRequest request) {
        String url = request.getServletPath();
        String pathInfo = request.getPathInfo();
        if (pathInfo != null) {
            url = org.springframework.util.StringUtils.hasLength(url) ? url + pathInfo : pathInfo;
        }
        return url;
    }
}

2.4 将拦截器注入

注意设置优先等级,否则执行顺序会有问题,导致获取不到设备信息。

@Configuration
public class FilterConfig {

    @Bean
    public AuthFilter AuthFilter() {
        return new AuthFilter();
    }

    @Bean
    public DeviceResolverRequestFilter deviceResolverRequestFilter(){
        return new DeviceResolverRequestFilter();
    }

    @Bean
    public FilterRegistrationBean<AuthFilter> authFilterRegistration() {
        FilterRegistrationBean<AuthFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(AuthFilter());
//        registration.addUrlPatterns(AppConst.APP_URL_PREFIX+"/*");
        registration.setOrder(1);
        return registration;
    }

    @Bean
    public FilterRegistrationBean<DeviceResolverRequestFilter> deviceResolverRequestFilterRegistration() {
        FilterRegistrationBean<DeviceResolverRequestFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(deviceResolverRequestFilter());
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return registration;
    }
}

2.5 UserUtil直接在请求中获取对应参数的工具类。包含直接的设备判定。

public class UserUtil {

    private UserUtil() {
    }

    // 全用户
    public static SysUser getUser() {
        return UserAccountHolder.getUser();
    }

    // officeId
    public static String getOfficeId() {
        SysUser user = UserAccountHolder.getUser();
        if (Objects.isNull(user)) {
            throw new BaseException(MessageEnum.ERROR_1010);
        }
        return user.getOfficeId();
    }

    // getUserId
    public static String getUserId() {
        SysUser user = UserAccountHolder.getUser();
        if (Objects.isNull(user)) {
            throw new BaseException(MessageEnum.ERROR_1010);
        }
        return user.getId();
    }

    // getToken
    public static String getToken() {
        SysUser user = UserAccountHolder.getUser();
        if (Objects.isNull(user)) {
            throw new BaseException(MessageEnum.ERROR_1010);
        }
        return user.getSecurityKey();
    }

    // getToken
    public static String getPassword() {
        SysUser user = UserAccountHolder.getUser();
        if (Objects.isNull(user)) {
            throw new BaseException(MessageEnum.ERROR_1010);
        }
        return user.getPassword();
    }

    // 修改用户
    public static void setUser(SysUser sysUser) {
        if (Objects.isNull(sysUser)) {
            throw new BaseException(MessageEnum.ERROR_1010);
        }
        UserAccountHolder.setUser(sysUser);
    }

    //获取isMobile
    public static boolean isMobile() {
        SysUser user = UserAccountHolder.getUser();
        if (Objects.isNull(user)) {
            throw new BaseException(MessageEnum.ERROR_1010);
        }
        return user.getDevice().isMobile();
    }

    //获取isNormal
    public static boolean isNormal() {
        SysUser user = UserAccountHolder.getUser();
        if (Objects.isNull(user)) {
            throw new BaseException(MessageEnum.ERROR_1010);
        }
        return user.getDevice().isNormal();
    }

    //获取isTablet
    public static boolean isTablet() {
        SysUser user = UserAccountHolder.getUser();
        if (Objects.isNull(user)) {
            throw new BaseException(MessageEnum.ERROR_1010);
        }
        return user.getDevice().isTablet();
    }
}

2.6 controller层应用 直接通过UserUtil就可直接获取信息,不再用添加HttpServletRequest,来再获取信息。当然也可以应用到对应的Service层都是可以直接获取的,同时设备信息也可以直接判定来不同实现。

  /**
     * 获取用户全部个人信息
     *
     * @return 用户个人信息
     */
    @GetMapping("/user")
    @ApiOperation(value = "1.0.4 查询个人全部信息接口", httpMethod = "GET")
    public ResultVo user() {
        SysUser sysUser = authService.user(UserUtil.getToken());
        return new ResultVo<>().setData(sysUser);
    }

3总结

对于相对于简单的权限管理这个足够用了。主要流程:1.注入设备获取相关的Bean.2.创建拦截器,获取token.3.对拦截器进行注入Bean,设置优先级。4.通过本地线程直接使用。