SpringSecurity+Token实现权限校验

1.Spring Security简介

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

1.1.需求

实现权限校验,指定用户可以访问指定页面

1.2代码解读分析

1).继承WebSecurityConfigurerAdapter

2).重写 configure 方法

 protected void configure(HttpSecurity httpSecurity)

注:此方法用来添加校验规则

/**
     * anyRequest          |   匹配所有请求路径
     * access              |   SpringEl表达式结果为true时可以访问
     * anonymous           |   匿名可以访问
     * denyAll             |   用户不能访问
     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
     * permitAll           |   用户可以任意访问
     * rememberMe          |   允许通过remember-me登录的用户访问
     * authenticated       |   用户登录后可访问
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // CSRF禁用,因为不使用session
                .csrf().disable()
                // 认证失败处理类
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 过滤请求
                .authorizeRequests()
                // 对于登录login 验证码captchaImage 允许匿名访问
                .antMatchers("/login", "/captchaImage").anonymous()
                .antMatchers(
                        HttpMethod.GET,
                        "/*.ttf",
                        "/*.woff",
                        "/*.gif",
                        "/*.eot",
                        "/*.json",
                        "/*.woff2",
                        "/*.png",
                        "/*.ico",
                        "/*.svg",
                        "/*.jpg",
                        "/*.html",
                        "/**/*.ttf",
                        "/**/*.woff",
                        "/**/*.gif",
                        "/**/*.eot",
                        "/**/*.json",
                        "/**/*.woff2",
                        "/**/*.png",
                        "/**/*.ico",
                        "/**/*.svg",
                        "/**/*.jpg",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js"
                ).permitAll().
                        antMatchers(
                        //mybatis复习相关的接口全部放行,同学们可以通过postMan进行测试而不需要进行权限认证
                        "/review/**",
                                "/review"
                ).permitAll()
                .antMatchers("/common/downloadByMinio**").permitAll()
                .antMatchers("/profile/**").anonymous()
                .antMatchers("/common/download**").anonymous()
                .antMatchers("/common/download/resource**").anonymous()
                .antMatchers("/webjars/**").anonymous()
                .antMatchers("/*/api-docs").anonymous()
                .antMatchers("/druid/**").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable();
        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
        // 添加JWT filter
        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 添加CORS filter
        httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
        httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
    }

上面有个jwt的过滤器

里面继承了一个类

如果请求中有token则放行

2).添加权限

那么 它是怎么添加权限的那?我们点进参数里看看...

可以看到里面是个接口,返回对象是个

咦。。。这个是不是放用户名和密码 和权限的那,那么看看它的实现类是怎么写的吧

   @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = userService.selectUserByUserName(username);
        if (StringUtils.isNull(user)) {
            log.info("登录用户:{} 不存在.", username);
            throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
        }
        else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
            log.info("登录用户:{} 已被删除.", username);
            throw new BaseException("对不起,您的账号:" + username + " 已被删除");
        }
        else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
            log.info("登录用户:{} 已被停用.", username);
            throw new BaseException("对不起,您的账号:" + username + " 已停用");
        }

        return createLoginUser(user);
    }

可以看到首先根据用户名去查对象,并做了一系列判断,最后通过之后调用了这个方法

这个完成对象的初始化,那么这个调用的service又是干什么的那?

这个是把该用户所对应的全部权限,从数据库中查询出来存入集合中,完成对象初始化的

我们再回到之前的配置类

至此,该用户的所拥有的全部权限就获取到了,完成了每个用户所看到的界面不一样的情况,但是这个还要结合一个注解来使用

@PreAuthorize("@ss.hasPermi('clues:course:list')")

这个注解为 spring security 的原生注解 里面返回布尔类型数据,为true就走这个方法

点进去有这么个方法,是不是很熟悉,集合参数里放的就是刚才该用户的权限列表集合,如果包含传进来的字符串,则返回true让其访问,至此功能就实现了

2.SpringBoot中JWT是如何使用的

 JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。中间用点(.)分隔成三个部分。注意JWT 内部是没有换行的。

2.1.优点

基于token的验证方式它有什么优点吗?

支持跨域访问,Cookie是不允许跨域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输.

无状态:Token机制在服务端不需要存储session信息,因为Token自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.

解耦 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可.

适用性更广:只要是支持http协议的客户端,就可以使用token认证。

服务端只需要验证token的安全,不必再去获取登录用户信息,因为用户的登录信息已经在token信息中。

基于标准化:你的API可以采用标准化的 JSON Web Token (JWT).

2.1.1.组成

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。中间用点(.)分隔成三个部分。注意JWT 内部是没有换行的。

2.2.使用

1).当前端传过来登录请求数据时,把对象生成Token存起来

    public String createToken(LoginUser loginUser) {
        String token = IdUtils.fastUUID();
        loginUser.setToken(token);
        setUserAgent(loginUser);
        refreshToken(loginUser);

        Map<String, Object> claims = new HashMap<>();
        claims.put(Constants.LOGIN_USER_KEY, token);
        //存放非敏感信息
        claims.put("username",loginUser.getUsername());
        claims.put("nickName",loginUser.getUser().getNickName());
        claims.put("createTime",loginUser.getUser().getCreateTime());
        return createToken(claims);
    }

这里用了两个方法

第一个:

    /**
     * 设置用户代理信息
     *
     * @param loginUser 登录信息
     */
    public void setUserAgent(LoginUser loginUser)
    {
        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
        loginUser.setIpaddr(ip);
        loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
        loginUser.setBrowser(userAgent.getBrowser().getName());
        loginUser.setOs(userAgent.getOperatingSystem().getName());
    }

设置代理信息

第二个:

 /**
     * 刷新令牌有效期
     *
     * @param loginUser 登录信息
     */
    public void refreshToken(LoginUser loginUser)
    {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
        // 根据uuid将loginUser缓存
        String userKey = getTokenKey(loginUser.getToken());
        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
    }

刷新令牌有效期,并将对象存入Redis中

最后调用了这个方法,返回生成的令牌

  /**
     * 从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    private String createToken(Map<String, Object> claims)
    {
        String token = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret).compact();
        return token;
    }

secret为签名,保证数据安全性

2).接下来我们看看如何取出来

可以看到我们传进去的是一个HttpServletRequest请求,因为我们把Token放请求头里了,那么我们点进去看看,是怎么取的

这个方法总共调用了这三个方法,最终取出来了user对象,接下来一步一步看吧

第一个方法获取Token字符串

第二个方法根据Token字符串获取集合

第三个方法根据Token存入的唯一的UUID获得存入缓存中的key

最终取到user对象,至此Token存取完成。

以上为个人梳理,有不对的地方还请指正

posted @ 2022-08-16 19:45  你会很厉害的  阅读(3889)  评论(2编辑  收藏  举报
//雪花飘落效果