基于 Spring Security 和 JWT 的权限系统

·  阅读 3322
基于 Spring Security 和 JWT 的权限系统

一、场景

前后端分离,数据交互该如何保证安全性?

某人:Token呀

移动端、小程序、H5等客户端同样是通过JSON数据传输,也涉及到身份校验问题

二、问题

Token到底如何实现,才是最好的方式?

身经百个项目(.(▼へ▼メ)装逼.) 见过不少项目是这样实现的

  • token?我们不需要
  • token写死的,后台提供给客户端(确实有不少)
  • token动态,后台通过某些算法生成token,提供给客户端(这种方式不是不行,只是不够完美)
  • 整个JSON数据加密传输,后台提供密钥(em....不评价)
  • 等等

三、解决方案

今天要讲解的是采用 JWT,解决Token问题

#科普一下JWT

JSON Web Token (JWT) 是在网络应用间传递信息的一种基于JSON的开放标准 (RFC 7519),用于作为JSON对象在不同系统之间进行安全地信息传输。主要使用场景一般是用来在 身份提供者和服务提供者间传递被认证的用户身份信息。

JWT由三部分组成,依次如下:

  • Header(头部) 包含两部分:token类型和采用的签名算法
  • Payload(负载) 是Token携带的用户自定义内容,数据是base64,非加密,可破解,不能存敏感信息;内容越多,token越长
  • Signature(签名) 将Header+Payload组成一个字符串,通过指定的签名算法进行计算,得到一个签名值,服务端可通过这个标志,来判断Token是否合法
#讲解Spring Security(附赠)

有JWT解决Token问题,再结合Spring Security 可以解决用户权限模块重重问题(真的很强大),当然除了Spring Security,还有Apache Shiro也可以解决权限,但没有Security强大,后续将写篇文章详解对比两者。

四、实现方式

pom.xml中引入 JWT 和 Spring Security 的依赖 jar

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
复制代码

JWT工具类,生成Token、验证Token、刷新Token等

@Component
public class JwtTokenUtil {

    private static final String CLAIM_KEY_USERNAME = "sub";
    private static final String CLAIM_KEY_CREATED = "created";
    
    // 生成token
    public String generateToken(UserDetails userDetails) {
        ...
    }
    
    // 刷新token
    public String refreshToken(String token) {
        ...
    }
    
    // 校验token
    public Boolean validateToken(String token) {
        ...
    }
    }
复制代码

Token过滤器, 处理接口的token

@Component
public class JwtTokenFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        String authHeader = request.getHeader(Constant.HEADER_STRING );
        if (authHeader != null && authHeader.startsWith(Constant.TOKEN_PREFIX )) {
            final String authToken = authHeader.substring(Constant.TOKEN_PREFIX.length() );
            String username = jwtTokenUtil.getUsernameFromToken(authToken);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
                            request));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(request, response);
    }
}
复制代码

Spring Security的核心配置

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userService;

    @Bean
    public JwtTokenFilter authenticationTokenFilterBean() throws Exception {
        return new JwtTokenFilter();
    }

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

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        String[] urls = new String[]{"/user/login", "/user/register"};
        httpSecurity.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                // 允许注册和登录接口不需token访问
                .antMatchers(urls).permitAll()
                .anyRequest().authenticated();
        httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
        httpSecurity.headers().cacheControl();
    }
}
复制代码

权限测试,接口需要限制访问限制,则添加 @PreAuthorize

@RestController
public class RoleController {

    /**
     * 测试普通权限
     *
     * @return
     */
    @PreAuthorize("hasAuthority('ROLE_NORMAL')")
    @GetMapping(value="/normal/test")
    public String test1() {
        return "普通角色访问";
    }

    /**
     * 测试管理员权限
     *
     * @return
     */
    @PreAuthorize("hasAuthority('ROLE_ADMIN')")
    @GetMapping(value = "/admin/test")
    public String test2() {
        return "管理员访问";
    }
}
复制代码

需给用户添加角色才能访问,如下图 用户角色关系表

用户角色关系表

五、Token如何校验

注意:JWT 默认是不加密的,任何人都可以读到,可以复制一串token到 JWT在线解密

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJPd2F0ZXIiLCJjcmVhdGVkIjoxNTYzMzc2ODE2NTIzLCJleHAiOjE1NjMzODQwMTZ9.SuL4dRoweriLOnZzohcEzUwXf7kVSx9KnTIGtB7ffuBtlUUFS1T8il7_fxv3Gn1LkX5DGOawqNhG4ZWYxALDig
复制代码

从上面可以看出,token存储的信息可以被解析出来的,所以大家不要把敏感信息存放这里

那token不会被篡改呢?不会,因为在token的三部分(Header、Payload、Signature) 中,Signature可以防止被篡改,这个密钥是只有服务端才知道

六、测试效果

  • 登录成功后返回token

  • 不带token的情况,会被拒绝访问

  • 正确访问方式

源码在此

分类:
后端
标签:
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改