springboot+jjwt+security完美解决rest

作者: 小尘哥 | 来源:发表于2018-08-19 22:02 被阅读251次

    微服务大行其道的现在,如果我们还在用wsdl之类的提供接口,给人的感觉就会很low,虽然说不能为了炫技而炫技,但是既然restful接口已经越来越流行,必然有它的道理。
    本文我们不讨论restful接口的好处,旨在解决使用restful时候的权限控制问题。

    springboot本身已经提供了很好的spring security的支持,我们只需要实现(或者重写)一部分接口来实现我们的个性化设置即可。本文浅显易懂,没有深入原理(后面文章会将,有需要的小伙伴稍等等~~~)。
    思路:
    1.通过spring security做授权拦截操作
    2.通过jwt根据用户信息生成token以供后面调用
    3.将生成的token放到HttpServletResponse头信息中
    4.使用的时候从response头中获取token放在request头中提交到后台做认证即可
    5.默认超时时间10天

    一、pom

    惯例还是先上pom,因为pom可以很直观的看到本项目用了哪些东西,我这个项目使用了很多的包,这里贴出了核心的几个,其他大部分的都会自动引用。

           <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-web</artifactId>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-config</artifactId>
                <scope>provided</scope>
            </dependency>
    
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
            </dependency>
    

    二、登录过滤器

    我们采用倒推法,用到什么找什么,这也比较符合XP编程的思想,不写多(无)余(用)的代码,既然要做认证,很明显需要一个过滤器来处理所有需要拦截的请求。
    UsernamePasswordAuthenticationFilter是security自己提供的过滤器,我们重写其中的成功方法(successfulAuthentication)来处理我们自己的逻辑,当然根据自己的情况,比如登录失败处理,重写(unsuccessfulAuthentication)即可。
    1.成功回调中用到一个TokenAuthenticationHandler,即token认证处理类,该类的主要方法就是借用jwt的机制来生成token,以供后面登录授权使用。
    2.往response头信息中放入参数为“Authorization”,值为“Bearer ”+token的值

    package com.mos.eboot.tools.jwt;
    
    import com.mos.eboot.tools.util.FastJsonUtils;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter{
        
        static final String TOKEN_PREFIX = "Bearer";
        static final String HEADER_STRING = "Authorization";
        
        private AuthenticationSuccessHandler successHandler;
    
        public JWTLoginFilter() {
        }
    
        public JWTLoginFilter(AuthenticationManager authManager) {
            setAuthenticationManager(authManager);
        }
    
        @Override
        protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException {
            TokenAuthenticationHandler tokenAuthenticationHandler = new TokenAuthenticationHandler();
            Object obj = auth.getPrincipal();
            if(obj != null) {
                UserDetails userDetails = (UserDetails)obj;
                String token = tokenAuthenticationHandler.generateToken(FastJsonUtils.toJSONNoConfig(userDetails));
                res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + token);
            }
            
            if(successHandler != null) {
                successHandler.onAuthenticationSuccess(req, res, auth);
            }
        }
    
        public void setSuccessHandler(AuthenticationSuccessHandler successHandler) {
            this.successHandler = successHandler;
        }
    }
    
    

    JWTAuthenticationToken

    package com.mos.eboot.tools.jwt;
    
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    
    import static java.util.Collections.emptyList;
    
    public class JWTAuthenticationToken extends UsernamePasswordAuthenticationToken{
        
        private static final long serialVersionUID = 1L;
        
        public JWTAuthenticationToken(Object principal) {
            super(principal,null,emptyList());
        }
    
        @Override
        public Object getCredentials() {
            return super.getCredentials();
        }
    
        @Override
        public Object getPrincipal() {
            return super.getPrincipal();
        }
    
    }
    
    

    TokenAuthenticationHandler

    package com.mos.eboot.tools.jwt;
    
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    
    import java.io.Serializable;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author 小尘哥
     */
    public class TokenAuthenticationHandler implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        private static final String CLAIM_KEY_CREATED = "created";
        private static final String CLAIM_KEY_SUBJECT = "subject";
    
        private static final String DEFAULT_SECRET = "eboot@secret";
        private static final Long DEFAULT_EXPIRATION = 864000L;
    
        private String secret = DEFAULT_SECRET;
        private Long EXPIRATION = DEFAULT_EXPIRATION;
    
        public TokenAuthenticationHandler() {
    
        }
    
        public String getSubjectFromToken(String token) {
            String subject;
            try {
                final Claims claims = getClaimsFromToken(token);
                subject = claims.get(CLAIM_KEY_SUBJECT).toString();
            } catch (Exception e) {
                subject = null;
            }
            return subject;
        }
    
    
        private Claims getClaimsFromToken(String token) {
            Claims claims;
            try {
                claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
            } catch (Exception e) {
                claims = null;
            }
            return claims;
        }
    
        private Date generateExpirationDate() {
            return new Date(System.currentTimeMillis() + EXPIRATION * 1000);
        }
    
        public String generateToken(String subject) {
            Map<String, Object> claims = new HashMap<String, Object>();
            claims.put(CLAIM_KEY_CREATED, new Date());
            claims.put(CLAIM_KEY_SUBJECT, subject);
            return generateToken(claims);
        }
    
        String generateToken( Map<String, Object> claims) {
            return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate())
                    .signWith(SignatureAlgorithm.HS512, secret).compact();
        }
    
    }
    
    

    三、认证

    从头信息中取出Authorization,然后解析出个人信息,如果个人信息不为空,则将个人信息加密后再放入授权域。

    package com.mos.eboot.tools.jwt;
    
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.web.filter.GenericFilterBean;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    
    public class JWTAuthenticationFilter extends GenericFilterBean {
        
        static final String HEADER_STRING = "Authorization";
        static final String TOKEN_PREFIX = "Bearer";
        
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
            
            HttpServletRequest req = (HttpServletRequest)request;
            
            String token = req.getHeader(HEADER_STRING);
            if(StringUtils.isNotBlank(token) && token.startsWith(TOKEN_PREFIX)) {
                TokenAuthenticationHandler tokenAuthenticationHandler = new TokenAuthenticationHandler();
                String subject = tokenAuthenticationHandler.getSubjectFromToken(token.replace(TOKEN_PREFIX, ""));
                if(StringUtils.isNotBlank(subject)) {
                    SecurityContextHolder.getContext().setAuthentication(new JWTAuthenticationToken(subject));
                }
            }
            filterChain.doFilter(request,response);
        }
    
    }
    
    

    四、调用

        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            http.csrf().disable().authorizeRequests().antMatchers("/**").authenticated()
                    .antMatchers(HttpMethod.POST, "/login").permitAll().anyRequest().permitAll().and()
                    .addFilterBefore(loginFilter(), UsernamePasswordAuthenticationFilter.class)
                    .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        }
    
    //注入登录校验类
        @Bean
        public JWTLoginFilter loginFilter() throws Exception {
            JWTLoginFilter loginFilter = new JWTLoginFilter(authenticationManager());
            loginFilter.setSuccessHandler(loginAuthenticationSuccessHandler);
            loginFilter.setAuthenticationFailureHandler((request, response, exception) -> {
                response.setContentType("application/json");
                response.getWriter().write(FastJsonUtils
                        .toJSONString(new ResultModel(ResultStatus.FAIL.getCode(), exception.getMessage())));
            });
            return loginFilter;
        }
    
        @Bean
        public DaoAuthenticationProvider authenticationProvider() {
            DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
            authenticationProvider.setUserDetailsService(userDetailsService());
            authenticationProvider.setPasswordEncoder(passwordEncoder());
            authenticationProvider.setHideUserNotFoundExceptions(false);
            return authenticationProvider;
        }
    
        @Bean
        @Override
        public UserDetailsService userDetailsService() {
            return new UserService();
        }
    
    //重写密码加密方法
        @Bean
        public Md5PasswordEncoder passwordEncoder() {
            Md5PasswordEncoder passwordEncoder = new Md5PasswordEncoder();
            passwordEncoder.setIterations(1);
            return passwordEncoder;
        }
    

    实现UserDetailsService 接口,定义自己的获取用户登录方法实现类

    package com.mos.eboot.api.config.support;
    
    import com.mos.eboot.api.platform.api.ISysUserService;
    import com.mos.eboot.platform.entity.SysUser;
    import com.mos.eboot.tools.result.ResultModel;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    
    /**
     * @author 小尘哥
     */
    @Service("userService")
    public class UserService implements IUserService {
    
        @Resource
        private ISysUserService sysUserService;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            return sysUserService.getByUsername(username);
        }
    }
    
    

    基本以上就可以搞定基础的无状态鉴权了,如果需要更深入的了解,比如权限自定义等,请关注我后面的文章.......

    相关文章

      网友评论

      本文标题:springboot+jjwt+security完美解决rest

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