专栏首页技术专栏Spring boot+Spring security+JJWT 实现restful风格的权限验证

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

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • eclipse/che 使用

    点击左侧commands栏分别运行jdbc:build 和jdbc:run命令,最后控制台打印helloworld说明测试成功

    Meet相识
  • 1.5 比特币的原理-为什么记账(挖矿)

    之前在将账户如何验证的时候,其实是把账户地址,交易信息进行hash打包的过程。这个过程是需要消耗计算机资源的,既然要消耗资源,那么节点为什么要参与记账呢。在比特...

    Meet相识
  • Python3入门机器学习(六)- 梯度下降法

    以下是定义了一个损失函数以后,参数theta对应的损失函数J的值对应的示例图,我们需要找到使得损失函数值J取得最小值对应的theta(这里是二维平面,也就是我们...

    Meet相识
  • 详解RxJava2 Retrofit2 网络框架简洁轻便封装

    RxJava2、Retrofit2火了有一段时间了,前段时间给公司的项目引入了这方面相关的技术,在此记录一下相关封装的思路。

    砸漏
  • springboot解决静态属性注入问题

    可以看到,当DSHWechatApiUtil工具类组件进行初始化时,调用@PostConstruct注解标注的方法,对静态变量进行了赋值。

    吟风者
  • Spring Boot 学习三:静态资源、整合 Thymeleaf 页面模板、@RestControllerAdvice

    在 Spring Boot 中,默认情况下,一共有5个位置可以放静态资源,五个路径分别是如下:

    关忆北.
  • Spring 中如何控制对象的初始化时间(延迟加载,强制先行加载)

    当标注了@Lazy 注解时候,不会看到 init user… 的输出。只有当首次使用 User 类的时候,才会被初始化。

    水货程序员
  • 一文教你实现 SpringBoot 中的自定义 Validator 和错误信息国际化配置

    本文通过示例说明,在 Springboot 中如何自定义 Validator,以及如何实现国际化的错误信息返回。注意,本文代码千万别直接照抄,有可能会出大事情的...

    程序猿石头
  • 【初识Go】| Day13 并发编程

    “Concurrency is about dealing with lots of things at once. Parallelism is about ...

    yussuy
  • Java 编程技巧之数据结构

    编写代码的"老司机"也是如此,"老司机"之所以被称为"老司机",原因也是"无他,唯手熟尔"。编码过程中踩过的坑多了,获得的编码经验也就多了,总结的编码技巧也就更...

    吴延宝

扫码关注云+社区

领取腾讯云代金券