Spring boot+Spring security+JJWT

作者: 9c0ddf06559c | 来源:发表于2017-08-10 19:36 被阅读566次

    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

    相关文章

      网友评论

      • Renky:github上面的项目只是起始项目而已,有点失望啊!
        Renky:@Meet相识_bfa5 嘻嘻~~!棒棒哒!!!:relaxed: :relaxed:
        9c0ddf06559c:代码已经提交
        9c0ddf06559c:@Renky 呀,貌似昨天写完demo 忘记提交了,不好意思不好意思,下午提交一下,好了我私信你

      本文标题:Spring boot+Spring security+JJWT

      本文链接:https://www.haomeiwen.com/subject/ccihrxtx.html