博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
springboot+jjwt+security完美解决restful接口无状态鉴权
阅读量:6345 次
发布时间:2019-06-22

本文共 9955 字,大约阅读时间需要 33 分钟。

微服务大行其道的现在,如果我们还在用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可以很直观的看到本项目用了哪些东西,我这个项目使用了很多的包,这里贴出了核心的几个,其他大部分的都会自动引用。

org.springframework.boot
spring-boot-starter-security
org.springframework.security
spring-security-web
provided
org.springframework.security
spring-security-config
provided
io.jsonwebtoken
jjwt

二、登录过滤器

我们采用倒推法,用到什么找什么,这也比较符合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
claims = new HashMap
(); claims.put(CLAIM_KEY_CREATED, new Date()); claims.put(CLAIM_KEY_SUBJECT, subject); return generateToken(claims); } String generateToken( Map
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);    }}

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

转载地址:http://ljjla.baihongyu.com/

你可能感兴趣的文章
YourSQLDba 配置——修改备份路径
查看>>
nginx web服务理论与实战
查看>>
java 库存 进销存 商户 多用户管理系统 SSM springmvc 项目源码
查看>>
网易音乐版轮播-react组件版本
查看>>
ES6 - 函数与剩余运算符
查看>>
你对position了解有多深?看完这2道有意思的题你就有底了...
查看>>
WebSocket跨域问题解决
查看>>
ECMAScript6基本介绍
查看>>
世界经济论坛发布关于区块链网络安全的报告
查看>>
巨杉数据库加入CNCF云原生应用计算基金会,共建开源技术生态
查看>>
Ubuntu 16.04安装Nginx
查看>>
从 JS 编译原理到作用域(链)及闭包
查看>>
flutter 教程(一)flutter介绍
查看>>
CSS面试题目及答案
查看>>
【从蛋壳到满天飞】JS 数据结构解析和算法实现-Arrays(数组)
查看>>
每周记录(三)
查看>>
Spring自定义注解从入门到精通
查看>>
笔记本触摸板滑动事件导致连滑的解决方式
查看>>
Runtime 学习:消息传递
查看>>
你了解BFC吗?
查看>>