首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring boot+Spring security+JJWT 实现restful风格的权限验证

Spring boot+Spring security+JJWT 实现restful风格的权限验证

作者头像
Meet相识
发布2018-09-12 16:08:51
3.5K2
发布2018-09-12 16:08:51
举报
文章被收录于专栏:技术专栏技术专栏

1. 引入相关依赖

<!-- JJWT -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.6.0</version>
        </dependency>
<!--security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

2.实体类准备

  • SysUser
   /** id */
    @Id
    private Integer id;
    /** 密码 */
    private String password;
    /** 用户名 */
    private String username;

    /**权限列表**/
    @Transient
    private List<SysRole> roles;
  • SysRole
   /** id */
    @Id
    private Integer id;
    /** name */
    private String name;
  • JwtUser
/**
 * security需要的UserDetails实现类
 */
@Data
public class JwtUser implements UserDetails {
    private static final long serialVersionUID = -4959252432107932674L;
    private final long id;
    private final String username;
    private final String password;
    /** 权限类.*/
    private final Collection<? extends GrantedAuthority> authorities;

    /**
     * 在createJwtFactory里注入
     */
    public JwtUser(long id,
                   String username,
                   String password,
                   Collection<? extends GrantedAuthority> authorities) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    @JsonIgnore
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    @JsonIgnore
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    @JsonIgnore
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    @JsonIgnore
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    @JsonIgnore
    public boolean isEnabled() {
        return true;
    }
}
  • JwtUserFactory
private JwtUserFactory() {
    }

    /**
     * 创建JwtUser工厂
     */
    public static JwtUser create(SysUser user){
        return new JwtUser(
                user.getId(),
                user.getUsername(),
                user.getPassword(),
                map2GrantedAuthorities(user.getRoles())
        );
    }

    /**
     * 讲User的List<Role>转换成JwtUser<GrantedAuthority>
     */
    private static List<GrantedAuthority>   map2GrantedAuthorities(List<SysRole> authorities){
        return authorities.stream()
                .map(e -> role2SimpleGrantedAuthority(e))
                .collect(Collectors.toList());
    }

    private static SimpleGrantedAuthority role2SimpleGrantedAuthority(SysRole role){
        return new SimpleGrantedAuthority(role.getName());
    }

3.实现UserDetailService接口

@Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        SysUser sysUser = userRepository.findByUserName(s);  //调用持久层从数据库获取用户信息
        if (sysUser == null)
            throw new UsernameNotFoundException("用户名不存在");
        List<SysRole> roles = sysRoleRepository.findRolesByUserId(sysUser.getId());  //根据用户id或者用户权限列表
        if (CollectionUtils.isEmpty(roles))
            roles = Collections.emptyList();
        sysUser.setRoles(roles);
        return JwtUserFactory.create(sysUser);
    }
4.由于前后端分离所以服务器端不能用session控制,这里需要在Security的过滤器开始之前,从header中取得jwt的token去jwt中进行校验,如果验证通过则在本次request中植入security需要的验证信息
  • security的前置jwt过滤器
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    /**
     * 这里注入的是前面写的UserDetailsService的实现类
     */
    @Autowired
    private UserDetailsService customUserService;

    @Value("${jwt.header}")
    private String tokenHeader;

    @Value("${jwt.tokenHead}")
    private String tokenHead;


    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain)
            throws ServletException, IOException {
        String authHeader = request.getHeader(this.tokenHeader);  // 取得header
        if (authHeader != null && authHeader.startsWith(tokenHead)) {  //判断header头
            final String authToken = authHeader.substring(tokenHead.length()); // The part after "Bearer "
            if (JwtUtil.getClaim(authToken) != null) {  //去jwt中获得验证token
                String username = JwtUtil.getClaim(authToken).getSubject();   //从jwt中获取信息,如果要缓存很多信息可以用Claims
                logger.info("checking authentication " + username);

                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

                    UserDetails userDetails = this.customUserService.loadUserByUsername(username);  //验证jwt的信息是否正确

                    //将验证信息放入SecurityContextHolder中,UsernamePasswordAuthenticationToken是Security验证账号密码的工具类
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
                            request));
                    logger.info("authenticated user " + username + ", setting security context");
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }

        chain.doFilter(request, response);
    }
}
  • 这是关键
//将验证信息放入SecurityContextHolder中,UsernamePasswordAuthenticationToken是Security验证账号密码的工具类
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
                            request));
                    logger.info("authenticated user " + username + ", setting security context");
                    SecurityContextHolder.getContext().setAuthentication(authentication);
  • jwt工具类
 /**
     * 根据token获取用户信息
     */
    public static Claims getClaim(String token){
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey("abcdefg")
                    .parseClaimsJws(token).getBody();
            return claims;
        }catch (Exception e){
            return null;
        }
    }

    /**
     * 设置用户信息进jwt
     */
    public static String setClaim(String subject){
        String token = Jwts
                .builder()
                .setSubject(subject)
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256, "abcdefg")
                .compact();
        return token;
    }
5 security配置类
@Configuration
@EnableWebSecurity   //开启WebSecurity支持
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启prePostEnabled注解支持
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService jwtUserDetailsServiceImpl;

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

    /**
     * 密码加密的bean,使用BCrypt
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 前置过滤器
     * @return
     */
    @Bean
    JwtAuthenticationTokenFilter authenticationTokenFilterBean(){
        return new JwtAuthenticationTokenFilter();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf()   
                .disable()  //禁用csrf保护
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)  //禁用session
                .and()
                .authorizeRequests()  //所有请求都要验证
                .antMatchers("/auth/**").permitAll()  //登录注册等请求过滤
                .antMatchers(
                        "/",
                        "/*.html",
                        "/favicon.ico",
                        "/**/*.html",
                        "/**/*.js",
                        "/**/*.css"
                ).permitAll()  //静态资源过滤
                .anyRequest().fullyAuthenticated()
                .and()
                .exceptionHandling()  //验证不通过的配置
                .authenticationEntryPoint(new RestAuthenticationEntryPoint())
                ;
        http   //添加前置过滤器
                .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
        http   //禁用header缓存
                .headers().cacheControl();
    }
}
6.RestAuthenticationEntryPoint
/**
 * 实现AuthenticationEntryPoint的commence方法自定义校验不通过的方法
 * Created by gaowenfeng on 2017/8/9.
 */
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        // 捕获AuthenticationException中的message,并封装成自定义异常抛出
        response.setCharacterEncoding("utf-8");
        response.getWriter().write( new Result().setCode(ResultCode.UNAUTHORIZED).setMessage(AuthErrorEnum.AUTH_HEADER_ERROR.getMessage()).toString());
    }
}
7.登录service
@Override
    public String login(String username, String password) {
        UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);
        final Authentication authentication = authenticationManager.authenticate(upToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        final String token = JwtUtil.setClaim(username);
        return token;
    }
8.登录controller
@RequestMapping(value = "/auth/login", method = RequestMethod.GET)
    public ResponseEntity<?> refreshAndGetAuthenticationToken(
            HttpServletRequest request){
        String token = request.getHeader(tokenHeader);
        String refreshedToken = authService.refresh(token);
        if(refreshedToken == null) {
            return ResponseEntity.badRequest().body(null);
        } else {
            return ResponseEntity.ok(refreshedToken);
        }
    }
9.最后附上github地址

https://github.com/MarkGao11520/spring-boot-security-restful

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 引入相关依赖
  • 2.实体类准备
  • 3.实现UserDetailService接口
    • 4.由于前后端分离所以服务器端不能用session控制,这里需要在Security的过滤器开始之前,从header中取得jwt的token去jwt中进行校验,如果验证通过则在本次request中植入security需要的验证信息
      • 5 security配置类
        • 6.RestAuthenticationEntryPoint
          • 7.登录service
            • 8.登录controller
              • 9.最后附上github地址
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档