前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringSecurity 入门 (三)

SpringSecurity 入门 (三)

作者头像
是小张啊喂
发布2021-08-09 17:39:00
3110
发布2021-08-09 17:39:00
举报
文章被收录于专栏:软件软件

废话不多说,直接开始这个SpringSecurity的学习项目。

数据库说明

用户信息表 USER 储存用户信息
代码语言:javascript
复制
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
    `id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'id',
    `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '名称',
    `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '密码',
    `isEnable` bit(1) NULL DEFAULT NULL COMMENT '是否启用1、启用 0、禁用',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
权限表PURVIEW储存权限信息
代码语言:javascript
复制
DROP TABLE IF EXISTS `purview`;
CREATE TABLE `purview`  (
    `id` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '权限id',
    `authority` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '权限名称',
    `role` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '角色',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
用户权限表PURVIEW储存用户权限
代码语言:javascript
复制
DROP TABLE IF EXISTS `authority`;
CREATE TABLE `authority`  (
    `id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '用户权限id',
    `authority` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '权限id',
    `member_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '用户id',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;

基础的Security安全配置

  1. 配置权限给SpringSecurity
  2. 配置是否需要拦截的请求
  3. 配置请求处理(success/error)
如何配置?

SecurityVerificationConfiguration配置类也是最为核心的一个类,在其中配置了关于上面的一些信息

代码语言:javascript
复制
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityVerificationConfiguration extends WebSecurityConfigurerAdapter {

    /**
     * 密码加密
     *
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 拦截器
     */
    @Autowired
    public JwtAuthenticationFilter jwtAuthenticationFilter;

    /**
     * jwt 验证处理器
     */
    @Autowired
    public JwtAccessDeniedHandler jwtAccessDeniedHandler;

    /**
     * toekn 配置
     */
    @Autowired
    public TokenConfiguration tokenConfiguration;

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

    /**
     * 授权 、 验证
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // 添加权限
        selectPurview(http);

        http
                .authorizeRequests()
                // 授权地址不需要验证
                .antMatchers("/auth/token").permitAll()
                // 用户注册地址
                .antMatchers("/user/registered").permitAll()
                // 其余的都需要校验
                .anyRequest().authenticated()
                .and()
                // 添加后置处理拦截器
                .addFilterAfter(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .exceptionHandling()
                // 访问拒绝处理程序
                .accessDeniedHandler(jwtAccessDeniedHandler)
                .and()
                .apply(tokenConfiguration)
                .and()
                // 取消 session 的状态
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .csrf().disable();
    }

    /**
     * 查询权限并将权限放入 security 中
     *
     * @param http
     * @throws Exception
     */
    public void selectPurview(HttpSecurity http) throws Exception {
        List<Purview> purviews = purviewService.selectList();
        for (Purview purview : purviews) {
            http.authorizeRequests()
                    .antMatchers(purview.getAuthority()).hasAnyAuthority(purview.getRole());
        }
    }

}

关于SecurityVerificationConfiguration说明希望帮你解决一些疑惑

  • @EnableWebSecurity注解开启web安全验证
  • @EnableGlobalMethodSecurity(prePostEnabled=true)启用基于注释的安全性 参考官方文档11.5 开启后可以通过 @PreAuthorize注解限制请求controller的访问权限
  • selectPurview(HttpSecurity http)方法查询所有的权限角色,并将权限角色交由SpringSecurity管理
  • PasswordEncoder密码加密 参考官方文档5.1.2
  • JwtAuthenticationFilterTOEKM拦截器

看了基础的配置,按照 SpringSecurity 入门(二)中提到的思路,需要构建 Authentication认证对象,那么下面就是构建认证所需要的 Authentication认证对象

如何构建?

在构建 Authentication认证对象之前,还需要明确一个问题,构建Authentication认证对象所需要的信息:

  1. principal 显然这个使用final修饰不可以修改,所以传递的值一定是在认证之后不需要修改的,例如:用户信息
  2. credentials用于防止认证的信息,可以是TOKEN
  3. authorities权限集合

principal 很简单构建一个用户就可以了,像这样

代码语言:javascript
复制
public User(String username, String password, Collection<? extends GrantedAuthority> authorities) 

credentials就更简单了,把jwt生成的 TOKEN放进去就好了

authorities权限集合,也不难,只需要查找用户拥有的权限就可以,这里有两种方式,一种是获取通过用户信息获取用户的权限,还有一种就是通过TOKEN获取权限信息,但是第一种的限制比较而言更多,第二种也更为方便,所有我们通过解析TOKEN来获取用户的权限

那首先应该如何构建这个TOKEN是首要的

构建生成TOKEN
代码语言:javascript
复制
@Component
@Slf4j
public class JwtUtil {

    /**
     * 签名密钥
     */
    @Value("${auth.token.signingKey}")
    private String signingKey;

    /**
     * 创建生成 token
     * <p>
     * setClaims() 与 setSubject() 冲突所以不设置主体信息
     *
     * @param claim 用户权限 map
     * @return String 生成的 token
     */
    public String createToken(Map<String, Object> claim) {
        return Jwts.builder()
                // 设置唯一的 ida
                .setId(IdUtil.simpleUUID())
//                .claim("auth", "admin")
                .setClaims(claim)
                // 设置过期时间
                .setExpiration(new DateUtil().getNowDateOneTime())
                // 设置 token 签发的时间
                .setIssuedAt(new DateTime())
                // 设置签名 使用HS256算法,并设置SecretKey(字符串)  签名算法和秘钥
                .signWith(SignatureAlgorithm.HS256, signingKey)
                // 以下内容构建JWT并将其序列化为紧凑的,URL安全的字符串
                .compact();
    }
    
}    

这里需要注意区分一下claim(String , Object)setClaims(Map<String, Object>)

  • claim只能设置单个权限
  • setClaims可以设置多个权限对象

这里建议第二种,这样可以从TOKEN中存放更多的有效信息

解析TOKEN构建Authentication认证对象
代码语言:javascript
复制
@Slf4j
@Component
public class TokenProvider {

    // 权限密钥
    private static final String AUTHORITIES_KEY = "auth";

    // 用户信息
    private static final String ID = "id";

    // 签名密钥
    @Value("${auth.token.signingKey}")
    private String signingKey;

    @Autowired
    JwtUtil jwtUtil;

    /**
     * 获取 Spring Context 的 SecurityContext 对象
     * 用于获取用户的身份验证
     *
     * @param token jwt 生成的 token 信息
     * @return authentication 认证对象
     */
    public Authentication getAuthentication(String token) {

        // parser() 解析token
        Claims claims = Jwts.parser()
                .setSigningKey(signingKey)
                .parseClaimsJws(token)
                .getBody();
        Object claim = claims.get(AUTHORITIES_KEY);

        // 权限
        String auth = "";
        if (Objects.nonNull(claim)) {
            auth = claim.toString();
        }

        // 权限集合
        Collection<? extends GrantedAuthority> authorities =
                Arrays.stream(auth.split(","))
                        .filter(StringUtils::isNotBlank)
                        .map(SimpleGrantedAuthority::new)
                        .collect(Collectors.toList());

        // 创建 Spring Security 的 user 对象
        User principal = new User((String) claims.get(ID), "", authorities);

        // 创建返回 Authentication 对象
        return new UsernamePasswordAuthenticationToken(principal, token, authorities);
    }

}

需要先从TOKEN中获取Claims对象,并且获取权限信息,先构建 principal用户信息,再通过用户信息构建一个Authentication认证对象,到这里基本上就完成了80%了,依照先前的思路完成了代码,但是有几个问题需要考虑

  1. 在什么时候构建TOKEN信息 当然是在用户登录的时候构建这样的一个安全认证信息的令牌,并且在访问时需要携带该令牌
  2. 在什么时候解析TOKEN信息,构建Authentication认证对象 当然是在每一次访问接口的时候

权限、用户信息,现在都交给Spring Security管理了,但是怎么实现在每一次访问接口的时候去构建这个Authentication认证对象呢?

提醒一下,拦截器,OncePerRequestFilter可以确保一次请求只会通过一次该过滤

过滤拦截请求
代码语言:javascript
复制
@Component
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    /**
     * 用户的业务逻辑层
     */
    @Autowired
    public UserService userService;

    @Autowired
    public JwtUtil jwtUtil;

    @Autowired
    private TokenProvider tokenProvider;

    public JwtAuthenticationFilter(TokenProvider tokenProvider) {
        this.tokenProvider = tokenProvider;
    }

    /**
     * 与{@code doFilter}的合同相同,但保证在单个请求线程中每个请求仅被调用一次。
     * 有关详细信息,请参见{@link #shouldNotFilterAsyncDispatch()}。
     * <p>提供HttpServletRequest和HttpServletResponse参数,而不是默认的ServletRequest和ServletResponse参数。
     *
     * @param httpServletRequest
     * @param httpServletResponse
     * @param filterChain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws IOException, ServletException {
         if (!"/auth/token".equals(httpServletRequest.getRequestURI())) {
            String token = httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION);
            if (token == null)
                throw new TokenException(HttpStatus.HTTP_FORBIDDEN, "缺少验证信息");

            Authentication authentication = tokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }

}

需要排除一些接口,例如登录认证接口,其余都需要构建这样Authentication认证对象

这就是按照我们思路实现的一套 认证,具体的操作表的代码就不展示了,按照思路往下去走就可以了。

Shao Jie :代码可能有些漏洞、BUG等等一些问题,逻辑也可能不够完美,只是提供一些思路,仅供参考,具体需要怎么做,自行参考官方文档,官方有更详细的解释,只是单纯的希望,能够给你帮助,代码问题可以在GitHubISSUES

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-08-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 数据库说明
    • 用户信息表 USER 储存用户信息
      • 权限表PURVIEW储存权限信息
        • 用户权限表PURVIEW储存用户权限
        • 基础的Security安全配置
          • 如何配置?
            • 如何构建?
              • 构建生成TOKEN
              • 解析TOKEN构建Authentication认证对象
              • 过滤拦截请求
          相关产品与服务
          多因子身份认证
          多因子身份认证(Multi-factor Authentication Service,MFAS)的目的是建立一个多层次的防御体系,通过结合两种或三种认证因子(基于记忆的/基于持有物的/基于生物特征的认证因子)验证访问者的身份,使系统或资源更加安全。攻击者即使破解单一因子(如口令、人脸),应用的安全依然可以得到保障。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档