Spring Security Jwt学习

缘由

最近的项目里遇到了spring security的内容。

通过查找资料,敲demo练习,做出了如下归纳总结。

实现spring-security-jwt

项目地址:https://github.com/ZhangLujie4/spring-security-jwt-demo

首先理一下大致思路(权限控制如何实现?)

  • 用户登录验证账号密码,根据用户生成jwt
  • 每次rest请求时会有filter对jwt进行解析验证,成功后保存这个Authentication(这一步就是权限控制)
  • 可以根据这个Authentication获得用户的标识信息

核心类

1.security的配置类(继承WebSecurityConfigurerAdapter)

在security的配置类里面主要实现三个configure方法

  • configure(AuthenticationManagerBuilder auth) user-detail相关配置
  • configure(WebSecurity web) 一般用来设置不需要权限过滤的文件
  • configure(HttpSecurity http) 核心,用来进行权限控制,过滤设置等重要的配置

1.1 configure(AuthenticationManagerBuilder auth)

常用的userdetails的相关配置有:

/** planA **/:
@Autowired
public void configureGlobal( AuthenticationManagerBuilder auth ) throws Exception {
    auth.userDetailsService( jwtUserDetailsService ) //jwtUserDetailsService为自定义UserDetailsService(继承于UserDetailsService)
        .passwordEncoder( passwordEncoder() );
}

/** planB **/
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
    // 使用自定义身份验证组件
    auth.authenticationProvider(new CustomAuthenticationProvider(userDetailsService, bCryptPasswordEncoder));
}

1.2 configure(WebSecurity web)

在这里可以举个例子,在demo中并未用到:

@Override
    public void configure(WebSecurity web) throws Exception {
        // TokenAuthenticationFilter will ignore the below paths
        web.ignoring().antMatchers(
                HttpMethod.POST,
                "/auth/login"
        );
        web.ignoring().antMatchers(
                HttpMethod.GET,
                "/",
                "/webjars/**",
                "/*.html",
                "/favicon.ico",
                "/**/*.html",
                "/**/*.css",
                "/**/*.js"
            );

    }

这里可以提以下web.ignoring()和http的permitAll()的区别:

WebSecurity主要是配置跟web资源相关的,比如css、js、images等等,但是这个还不是本质的区别,关键的区别如下:

  • 前者设置后不经过spring security的过滤器
  • 后者还是需要经过spring security的过滤器(其中包含登录的和匿名的)。

1.3 configure(HttpSecurity http)

我的demo中实现了第三个configure,具体如下:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .headers().frameOptions().disable().and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            .antMatchers("/api/user/**").permitAll()
            .antMatchers("/api/admin/**").hasAuthority("ROLE_ADMIN")
            .and()
            .addFilterBefore(new JwtFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class);
    }

addFilterBefore主要用来实现自定义jwtfilter的设置。

扫描二维码关注公众号,回复: 2864080 查看本文章

当然也可以用apply()方法添加一些配置(其中包含jwtfilter)。

2.自定义的Filter

2.1

demo中的filter实现具体如下:

/**
 * @author tori
 * 2018/7/30 下午2:06
 */
public class JwtFilter extends GenericFilterBean {

    public static final String AUTH_HEADER = "Authentication";
    public static final String AUTH_PREFIX = "Bearer ";

    private TokenProvider tokenProvider;

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

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
        String jwt = getAuthHeader(httpServletRequest);
        if (StringUtils.hasText(jwt)) {
            if (tokenProvider.validateToken(jwt)) {
                Authentication authentication = tokenProvider.getAuthentication(jwt);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    private String getAuthHeader(HttpServletRequest request) {
        String bearerToken = request.getHeader(AUTH_HEADER);
        String token = null;
        if (bearerToken != null && bearerToken.startsWith(AUTH_PREFIX)) {
            token = bearerToken.substring(7);
        }
        return token;
    }
}

filter的作用:

  • 读取到请求头中的授权的token

  • 从token解析出Authentication对象,将其保存到上下文里

    SecurityContextHolder.getContext().setAuthentication(authentication)

  • Authentication作为之后判断用户权限的依据。

一般情况下我们自定义一个类实现Authentication接口,这样我们就可以通过解析Authentication获取到我们需要的成员属性值了。(getPrincipal)

当然,你也可以选择用其他方式实现自定义的Authentication

步骤一

一个UserDetails的实现类

public class User implements UserDetails {
    ......
}

步骤二

一个AbstractAuthenticationToken的继承类

public class TokenBasedAuthentication extends AbstractAuthenticationToken {

    private String token;
    private final UserDetails principle;
    ......
}

因为AbstractAuthenticationToken也是实现了Authentication,所以这个类就可以当做Authentication作为jwt的解析对象。

补充

@Autowired
public void configureGlobal( AuthenticationManagerBuilder auth ) throws Exception {
    auth.userDetailsService( jwtUserDetailsService )
        .passwordEncoder( passwordEncoder() );
}

这里的jwtUserDetailsService是一个实现了UserDetailsService的类,可以在里面做权限对象相关的操作(修改密码,通过username查询数据库等)

@Autowired
private PasswordEncoder passwordEncoder;

@Autowired
private AuthenticationManager authenticationManager;

这两个是比较常用的自动装载的bean

3.TokenProvider

这个当然是最重要的了,用来提供生成和解析token方法的类

生成token

public String createToken(UserAuthentication userAuthentication) {
        String authorities = userAuthentication.getAuthorities().stream()
                .map(auth -> auth.getAuthority()).collect(Collectors.joining(","));

    long now = (new Date()).getTime();

    return JwtFilter.AUTH_PREFIX
        + Jwts.builder().setId(String.valueOf(userAuthentication.getUserId()))
        .setSubject(JSON.toJSONString(userAuthentication.getPrincipal()))//在这里可以存入序列化后的对象
        .claim(AUTHORITY_KEY, authorities)//这里可以存入key和value,value代表权限role的list
        .signWith(SignatureAlgorithm.HS512, SECRET_KEY) //秘钥和加密算法
        .setExpiration(new Date(now + EXPIRES_IN))//设置过期时间
        .compact();
}

解析token获得Authentication

public Authentication getAuthentication(String jwt) {
    Claims claims = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(jwt).getBody();
    Collection<? extends GrantedAuthority> authorities = Arrays.asList(claims.get(AUTHORITY_KEY).toString().split(","))
        .stream().map(authority -> new SimpleGrantedAuthority(authority)).collect(Collectors.toList());
    SecurityUser user = JSON.parseObject(claims.get(Claims.SUBJECT).toString(), SecurityUser.class);
    user.setAuthorities(authorities);
    UserAuthentication authentication = new UserAuthentication(user);

    return authentication;
}

我这里是token可以解析出整个对象,你也可以用token仅仅解析出username(唯一标识),用于后续的授权处理操作。

以上。

猜你喜欢

转载自blog.csdn.net/qq_33235059/article/details/81387711