Spring Boot + Spring Security +JWT实现登录以及token验证

作者: 小疯子 分类: Spring Boot 发布时间: 2019-01-10 12:05

前期废话:当时费劲精力写完这个功能就没有精力整理一下,然而现在开发的过程忘的差不多了,趁还有些记忆把开发的过程记录一下;刚开始接触java就先实现个登录,里面有很多知识点还不知道怎么回事,也就是找资料堆代码实现这个功能,后期等了解清楚再进行补充

一、名词介绍

  1. JWT(JSON Web Token)
    关于JWT介绍说是是目前最流行的跨域身份验证解决方案。是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).定义了一种简洁的,自包含的方法用于通信双方之间以JSON对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。
    详细了解可以去官网地址的介绍查看:https://jwt.io/introduction/
    全面解读、使用步骤当时我是参考了这篇博客:https://blog.csdn.net/achenyuan/article/details/80829401
  2. Spring Security
    Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。
    一个能够为基于Spring的企业应用系统提供声明式的安全訪问控制解决方式的安全框架(简单说是对访问权限进行控制嘛),应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。   spring security的主要核心功能为 认证和授权,所有的架构也是基于这两个核心功能去实现的。
    还是上一下官网的介绍地址:https://spring.io/projects/spring-security
    参考其他中文博客:https://blog.csdn.net/liushangzaibeijing/article/details/81220610
    原理参考此博客的前半部分讲的比较详细一些:https://www.cnblogs.com/xiongyu/archive/2012/07/21/2602220.html

二、开发过程

  1. 依赖添加
    在项目的pom.xml中添加如下依赖:

    <!-- spring security -->
    
    <dependency>
    
    <groupId>org.springframework.boot</groupId>
    
    <artifactId>spring-boot-starter-security</artifactId>
    
    <version>2.1.0.RELEASE</version>
    
    </dependency>
    
    <dependency>
    
    <groupId>io.jsonwebtoken</groupId>
    
    <artifactId>jjwt</artifactId>
    
    <version>0.9.1</version>
    
    </dependency>

    关于jjwt的版本可以从https://github.com/jwtk/jjwt中得到最新的。

  2. 用户登录——新建一个用于用户登录的授权验证,要实现UserDetails接口,包含 GrantedAuthority 的 UserDetails对象在构建 Authentication对象时填入数据。相关学习链接:Spring Security身份认证之UserDetailsService(https://blog.csdn.net/my_learning_road/article/details/79833802)
    原理说明:实现UserDetailsService,loadUserByUsername(String userName)方法,根据userName来实现自己的业务逻辑返回UserDetails的实现类,需要自定义User类实现UserDetails,比较重要的方法是getAuthorities(),用来返回该用户所拥有的权限。
    贴用户类代码如下:

    package com.xiaofeng.pro.entity;
    
    import com.xiaofeng.pro.base.MyBaseEntity;
    import com.xiaofeng.pro.common.constant.CommonConstant;
    import lombok.Data;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    import tk.mybatis.mapper.util.StringUtil;
    
    import javax.persistence.*;
    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;
    
    /**
     * 先给个username\passwd
     */
    @Data
    @Entity
    @Table(name = "t_user")
    public class User extends MyBaseEntity implements UserDetails {
        private static final long serialVersionUID = 1L;
    
    //    @Id
    //    @GeneratedValue(strategy = GenerationType.IDENTITY)
    //    private Long id;
    
        @Column(unique = true)
        private String username;
    
        private String password;
    
        private String nickName;
    
        private String mobile;
    
        private String email;
    
        private String address;
    
        private String street;
    
        private Integer sex;
    
        private Long organizationId;
    
        @Column(length=2)
        private String passStrength;
    
        @Column(length=1000)
        private String avatar = CommonConstant.USER_DEFAULT_AVATAR;;// 要留意用户默认头像会作为Mapper的查询条件
    
        //@ApiModelProperty(value = "用户类型 0普通用户 1管理员")
        private Integer type = CommonConstant.USER_TYPE_NORMAL;
    
    //    @ApiModelProperty(value = "状态 默认0正常 -1拉黑")
        private Integer status = CommonConstant.USER_STATUS_NORMAL;
    
    //    @ApiModelProperty(value = "描述/详情/备注")
        private String description;
    
    //    @ApiModelProperty(value = "所属部门id")
        private String departmentId;
    
        @Transient
        private String departmentTitle;
    
        @Transient
        private List<Role> roles;
    
        @Transient
        private List<Permission> permissions;
    
    
    
        /** 
        * @Description: 自己猜这个功能应该是将用户拥有的角色权限、请求权限添加进来的意思吧,将用户与角色请求权限关联起来,用来返回该用户所拥有的权限
        * @Param: [] 
        * @return: java.util.Collection<? extends org.springframework.security.core.GrantedAuthority> 
        * @Author: xiaofeng
        * @Date: 2018/11/29 
        */
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            List<GrantedAuthority> authorityList = new ArrayList<>();
            List<Permission> permissions = this.getPermissions();
            // 添加请求权限
            if(permissions!=null&&permissions.size()>0){
                for (Permission permission : permissions) {
                    if(CommonConstant.PERMISSION_OPERATION.equals(permission.getType())
                            && StringUtil.isNotEmpty(permission.getTitle())
                            &&StringUtil.isNotEmpty(permission.getPath())) {
                        authorityList.add(new SimpleGrantedAuthority(permission.getTitle()));
                    }
                }
            }
            // 添加角色
            List<Role> roles = this.getRoles();
            if(roles!=null&&roles.size()>0){
                // lambda表达式
                roles.forEach(item -> {
                    if(StringUtil.isNotEmpty(item.getName())){
                        authorityList.add(new SimpleGrantedAuthority(item.getName()));
                    }
                });
            }
            return authorityList;
        }
    
        /**
         * 账户是否过期
         * @return
         */
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        /**
         * 是否禁用
         * @return
         */
        @Override
        public boolean isAccountNonLocked() {//常量自己设定就可以
            return CommonConstant.USER_STATUS_LOCK.equals(this.getStatus()) ? false : true;
        }
    
        /**
         * 密码是否过期
         * @return
         */
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        /**
         * 是否启用
         * @return
         */
        public boolean isEnabled() {
            return CommonConstant.USER_STATUS_NORMAL.equals(this.getStatus()) ? true : false;
        }
    }
  3. 用户登录——然后就是UserDetailsService的实现了,前面说了这个实现类的作用是根据userName来实现返回UserDetails的实现类,代码很简单如下:
    package com.xiaofeng.pro.config.security;
    
    import com.xiaofeng.pro.entity.User;
    import com.xiaofeng.pro.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Component;
    
    /**
     * @program: people
     * @description: 自定义userservice实现认证
     * @author: dongxiaofeng
     * @create: 2018-11-29-10-05
     */
    @Component
    public class UserDetailsServiceImpl implements UserDetailsService {
    
        @Autowired
        private UserService userService;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            UserDetails user = userService.findByUsername(username);
            if(null == user){
                throw new UsernameNotFoundException(username);
            }
            return user;
    
        }
    }
  4. 用户登录——登录成功处理器AuthenticationSuccessHandler继承了SavedRequestAwareAuthenticationSuccessHandler抽象类,登录成功返回加密的token返回代码如下:
    package com.xiaofeng.pro.config.security.jwt;
    
    import com.google.gson.Gson;
    import com.xiaofeng.pro.common.constant.SecurityConstant;
    import com.xiaofeng.pro.common.utils.JwtTokenUtil;
    import com.xiaofeng.pro.common.utils.ResponseUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * 登录成功处理类
     */
    @Component
    public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    
        @Autowired
        private JwtTokenUtil jwtTokenUtil;
    
        /** 
        * @Description: 登录系统,成功后生成token返还给客户端
        * @Param: [request, response, authentication] 
        * @return: void 
        * @Author: xiaofeng
        * @Date: 2018/11/27 
        */
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
            UserDetails userDetails = (UserDetails)authentication.getPrincipal();//principal用户基本信息
            String username = userDetails.getUsername();
            List<GrantedAuthority> list = (List<GrantedAuthority>)userDetails.getAuthorities();
            List<String> authorities = new ArrayList<>();
            for(GrantedAuthority g : list){
                authorities.add(g.getAuthority());
            }
            ///////////ipInfoUtil.getUrl(request);
            Map<String, Object> claims = new HashMap<>();
            claims.put(SecurityConstant.AUTHORITIES,new Gson().toJson(authorities)); //看不懂的
            String token = jwtTokenUtil.generateToken(claims,username);
            token = SecurityConstant.TOKEN_SPLIT + token;
    
            ResponseUtil.out(response, ResponseUtil.resultMap(true, 200, "登录成功", token));
    
        }
    }

    其中的JwtTokenUtil代码如下:

    package com.xiaofeng.pro.common.utils;
    
    import com.xiaofeng.pro.common.constant.SecurityConstant;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import org.springframework.stereotype.Component;
    
    import java.io.Serializable;
    import java.util.Date;
    import java.util.Map;
    
    /**
     * @program: people
     * @description: jwt工具类
     * @author: dongxiaofeng
     * @create: 2018-11-27-11-58
     */
    @Component
    public class JwtTokenUtil implements Serializable {
        private Integer tokenExpireTime = 60; //先设置个60分钟的 token过期时间
    
        //生成token,还通过SecurityConstant.JWT_SIGN_KEY进行签名?
        public String generateToken(Map<String, Object> claims,String subject){
            return Jwts.builder()
                    .setClaims(claims)
                    .setSubject(subject)
                    .setExpiration(new Date(System.currentTimeMillis() + tokenExpireTime * 60 * 1000))
                    .signWith(SignatureAlgorithm.HS512, SecurityConstant.JWT_SIGN_KEY)
                    .compact();
    
        }
    }
    
    

     

  5. 用户登录——登陆失败处理器
    目前代码很简单,就是失败了返回个验证失败的消息

    package com.xiaofeng.pro.config.security.jwt;
    
    import com.xiaofeng.pro.common.utils.ResponseUtil;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
    * @program: people
    * @description: 验证失败
    * @author: dongxiaofeng
    * @create: 2018-11-27-17-31
    */
    @Component
    public class AuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {
    
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
    ResponseUtil.out(response, ResponseUtil.resultMap(false, 500, "验证失败"));//这里需要细分失败的原因按照exception
    }
    }
    以上就是登录的过程了算是,包含了成功失败的处理,关于这两个的配置在下方的WebSecurityConfig类中来进行配置——.successHandler(successHandler).failureHandler(failureHandler)

    理一下大体流程:userDetailsService登录->成功调用successhandler生成加密的token->失败调用failurehandler

  6.  访问拦截——其他路径来进行访问的话,大体讲一下流程需要通过拦截器进行拦截myFilterSecurityInterceptor,这个拦截器有使用了MyAccessDecisionManager和FilterInvocationSecurityMetadataSource这两个类,一个FilterInvocationSecurityMetadataSource是用来获取所有的权限,并且权限与路径联合,另一个MyAccessDecisionManager就是判断路径与权限是否允许通过来判断用户访问的路由是否能正常有权限的访问
    MyFilterSecurityInterceptor :

    package com.xiaofeng.pro.config.security.permission;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.access.SecurityMetadataSource;
    import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
    import org.springframework.security.access.intercept.InterceptorStatusToken;
    import org.springframework.security.web.FilterInvocation;
    import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.*;
    import java.io.IOException;
    
    /**
     * @program: people
     * @description: 权限管理过滤器 监控用户行为
     * @author: dongxiaofeng
     * @create: 2018-11-27-17-35
     */
    @Component
    public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
    
        @Autowired
        private FilterInvocationSecurityMetadataSource securityMetadataSource;
    
        @Autowired
        public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager){
            super.setAccessDecisionManager(myAccessDecisionManager);
        }
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            FilterInvocation fi = new FilterInvocation(servletRequest,servletResponse,filterChain);//FilterInvocation就是把doFilter传进来的request,response和FilterChain对象保存起来,供FilterSecurityInterceptor的处理代码调用。
            invoke(fi);
        }
    
        public void invoke(FilterInvocation fi) throws IOException, ServletException {
            InterceptorStatusToken token = super.beforeInvocation(fi);//会调用我们定义的accessDecisionManager:decide(Object object)和securityMetadataSource:getAttributes(Object object)方法。
            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            } finally {
                super.afterInvocation(token, null); //afterInvocation()方法实现了对返回结果的处理,在注入了AfterInvocationManager的情况下默认会调用其decide()方法
            }
        }
    
        @Override
        public void destroy() {
    
        }
    
        @Override
        public Class<?> getSecureObjectClass() {
            return FilterInvocation.class;
        }
    
        @Override
        public SecurityMetadataSource obtainSecurityMetadataSource() {
            return this.securityMetadataSource;
        }
    }

    MySecurityMetadataSource :

    package com.xiaofeng.pro.config.security.permission;
    
    import com.xiaofeng.pro.common.constant.CommonConstant;
    import com.xiaofeng.pro.entity.Permission;
    import com.xiaofeng.pro.service.PermissionService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.access.ConfigAttribute;
    import org.springframework.security.access.SecurityConfig;
    import org.springframework.security.web.FilterInvocation;
    import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
    import org.springframework.stereotype.Component;
    import org.springframework.util.AntPathMatcher;
    import org.springframework.util.PathMatcher;
    import tk.mybatis.mapper.util.StringUtil;
    
    import java.util.*;
    
    /**
     * @program: people
     * @description: 权限资源管理器
     * 为权限决断器提供支持
     * 资源角色授权器需要实现FilterInvocationSecurityMetadataSource接口。
     * 请求的资源所需要的角色权限在服务器启动时候就已经确定的,所以在该实现类的构造方法中需要确定每一种资源需要那些角色权限,
     * 通过一个Map<String, List<ConfigAttribute>>即可将所有资源所需要的List<ConfigAttribute>存储起来。
     * 该实现类中getAttributes(Object arg0)方法,可以通过请求的url返回对应的Collection<ConfigAttribute>,
     * 通过传进来的FilterInvocation可以得到RequestUrl,然后遍历Map<String, List<ConfigAttribute>>。
     * @author: dongxiaofeng
     * @create: 2018-11-28-15-17
     */
    @Component
    public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    
        @Autowired
        private PermissionService permissionService;
    
        private Map<String, Collection<ConfigAttribute>> map = null;
    
        /**
         * 加载权限表中所有操作请求权限
         */
        public void loadResourceDefine(){
            map = new HashMap<>(16);
            Collection<ConfigAttribute> configAttributes;
            ConfigAttribute cfg;
            // 获取启用的权限操作请求
            List<Permission> permissions = permissionService.findByTypeAndStatusOrderBySortOrder(CommonConstant.PERMISSION_OPERATION, CommonConstant.STATUS_NORMAL);
            for(Permission permission : permissions) {
                if(StringUtil.isNotEmpty(permission.getTitle())&&StringUtil.isNotEmpty(permission.getPath())){
                    configAttributes = new ArrayList<>();
                    cfg = new SecurityConfig(permission.getTitle());
                    //作为MyAccessDecisionManager类的decide的第三个参数
                    configAttributes.add(cfg);
                    //用权限的path作为map的key,用ConfigAttribute的集合作为value,也就是path与关联的配置属性,啥意思我也懵逼,应该是path是个路径,路径对应的权限的名字
                    map.put(permission.getPath(), configAttributes);
                }
            }
        }
    
        /**
         * 判定用户请求的url是否在权限表中
         * 如果在权限表中,则返回给decide方法,用来判定用户是否有此权限
         * 如果不在权限表中则放行
         * @param o
         * @return
         * @throws IllegalArgumentException
         */
        @Override
        public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
    
            if(map == null){
                loadResourceDefine();
            }
            //Object中包含用户请求request
            String url = ((FilterInvocation) o).getRequestUrl();
            PathMatcher pathMatcher = new AntPathMatcher(); //pathmatcher的介绍链接:https://www.cnblogs.com/hypnotizer/p/7085399.html
            Iterator<String> iterator = map.keySet().iterator();
            while (iterator.hasNext()) {
                String resURL = iterator.next();
                if (StringUtil.isNotEmpty(resURL)&&pathMatcher.match(resURL,url)) {
                    return map.get(resURL);
                }
            }
            return null;
        }
    
        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            return null;
        }
    
        @Override
        public boolean supports(Class<?> aClass) {
            return true;
        }
    
    }

    MyAccessDecisionManager :

    package com.xiaofeng.pro.config.security.permission;
    
    import org.springframework.security.access.AccessDecisionManager;
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.access.ConfigAttribute;
    import org.springframework.security.authentication.InsufficientAuthenticationException;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.stereotype.Component;
    
    import java.util.Collection;
    import java.util.Iterator;
    
    /**
     * AccessDecisionManager(决策管理器)的实现需要实现AccessDecisionManager接口,
     * 在实现的decide(Authentication arg0, Object arg1,Collection<ConfigAttribute> arg2)方法中,
     * 需要将用户具有的角色权限Collection<GrantedAuthority> grantedAuthorities=arg0.getAuthorities();
     * 与访问该资源所需要的金额角色权限Collection<ConfigAttribute> arg2进行比较,若有一个角色匹配,
     * 则放行允许该用户访问此资源。
     * @program: people
     * @description: 权限管理决断器 判断用户拥有的权限或角色是否有资源访问权限
     * @author: dongxiaofeng
     * @create: 2018-11-28-11-58
     */
    @Component
    public class MyAccessDecisionManager implements AccessDecisionManager {
        @Override
        public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
            if(collection == null){//资源不需要权限就可以访问
                return;
            }
            Iterator<ConfigAttribute> iterator = collection.iterator();
            while (iterator.hasNext()){
                ConfigAttribute c = iterator.next();
                String needPerm = c.getAttribute();//获取访问资源需要的权限
                for(GrantedAuthority ga : authentication.getAuthorities()){
                    //匹配用户拥有的角色权限和需要的角色权限
                    if(needPerm.trim().equals(ga.getAuthority())){
                        return;
                    }
                }
            }
            throw new AccessDeniedException("抱歉,您没有访问权限");
        }
    
        /**
         * 表示当前AccessDecisionManager是否支持对应的ConfigAttribute
         * @param configAttribute
         * @return
         */
        @Override
        public boolean supports(ConfigAttribute configAttribute) {
            return true;
        }
    
        /**
         * 表示当前AccessDecisionManager是否支持对应的受保护对象类型
         * @param aClass
         * @return
         */
        @Override
        public boolean supports(Class<?> aClass) {
            return true;
        }
    }

  7.  访问拦截——上面是进行用户权限路径访问的获取和验证,如果验证后没有权限,这里给出一个访问拒绝处理器RestAccessDeniedHandler,代码如下:
    package com.xiaofeng.pro.config.security.jwt;
    
    import com.xiaofeng.pro.common.utils.ResponseUtil;
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.web.access.AccessDeniedHandler;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * @program: people
     * @description: 如果用户已经通过身份验证,试图访问受保护的(该用户没有权限的)资源
     * @author: dongxiaofeng
     * @create: 2018-11-27-17-33
     */
    @Component
    public class RestAccessDeniedHandler implements AccessDeniedHandler {
        @Override
        public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
            ResponseUtil.out(httpServletResponse,ResponseUtil.resultMap(false,403,"抱歉,您没有访问权限"));
        }
    }

     

  8. JWT过滤——以上是权限过滤验证,下面添加上JWT过滤器进行token验证,代码如下
    package com.xiaofeng.pro.config.security.jwt;
    
    import com.google.gson.Gson;
    import com.google.gson.reflect.TypeToken;
    import com.xiaofeng.pro.common.constant.SecurityConstant;
    import com.xiaofeng.pro.common.utils.ResponseUtil;
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.ExpiredJwtException;
    import io.jsonwebtoken.Jwts;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.web.AuthenticationEntryPoint;
    import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
    import tk.mybatis.mapper.util.StringUtil;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @program: people
     * @description: JWT过滤器
     * @author: dongxiaofeng
     * @create: 2018-11-27-17-36
     */
    @Slf4j
    public class JWTAuthenticationFilter extends BasicAuthenticationFilter {
        public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
            super(authenticationManager);
        }
    
        public JWTAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
            super(authenticationManager, authenticationEntryPoint);
        }
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
    
            String header = request.getHeader(SecurityConstant.HEADER);
            if(StringUtil.isEmpty(header)){
                header = request.getParameter(SecurityConstant.HEADER);
            }
            if (StringUtil.isEmpty(header) || !header.startsWith(SecurityConstant.TOKEN_SPLIT)) {
                chain.doFilter(request, response);
                return;
            }
            try {
                UsernamePasswordAuthenticationToken authentication = getAuthentication(request, response);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }catch (Exception e){
                e.toString();
            }
    
            chain.doFilter(request, response);
        }
    
        private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request, HttpServletResponse response) {
    
            String token = request.getHeader(SecurityConstant.HEADER);
    
            if (StringUtil.isNotEmpty(token)) {
                // 解析token
                Claims claims = null;
                try {
                    claims = Jwts.parser()
                            .setSigningKey(SecurityConstant.JWT_SIGN_KEY)
                            .parseClaimsJws(token.replace(SecurityConstant.TOKEN_SPLIT, ""))
                            .getBody();
                    //获取用户名
                    String username = claims.getSubject();
    
                    //获取权限
                    List<GrantedAuthority> authorities = new ArrayList<>();
                    String authority = claims.get(SecurityConstant.AUTHORITIES).toString();
                    if(StringUtil.isNotEmpty(authority)){
                        List<String> list = new Gson().fromJson(authority, new TypeToken<List<String>>(){}.getType());
                        for(String ga : list){
                            authorities.add(new SimpleGrantedAuthority(ga));
                        }
                    }
                    if(StringUtil.isNotEmpty(username)) {
                        User principal = new User(username, "", authorities);
                        return new UsernamePasswordAuthenticationToken(principal, null, authorities);
                    }
                } catch (ExpiredJwtException e) {
                    throw new RuntimeException("登录已失效,请重新登录");//参考的代码是自定义了个对应的exception
                } catch (Exception e){
                    ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"解析token错误"));
                }
            }
            return null;
        }
    }
  9. security核心配置类WebSecurityConfigurerAdapter 提供了一种便利的方式去创建 WebSecurityConfigurer的实例,只需要重写 WebSecurityConfigurerAdapter 的方法,即可配置拦截什么URL、设置什么权限等安全控制。
    贴代码如下:package com.xiaofeng.pro.config.security;import com.xiaofeng.pro.config.security.jwt.AuthenticationFailHandler;import com.xiaofeng.pro.config.security.jwt.AuthenticationSuccessHandler;

    import com.xiaofeng.pro.config.security.jwt.JWTAuthenticationFilter;

    import com.xiaofeng.pro.config.security.jwt.RestAccessDeniedHandler;

    import com.xiaofeng.pro.config.security.permission.MyFilterSecurityInterceptor;

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.context.annotation.Configuration;

    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;

    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

    import org.springframework.security.config.annotation.web.builders.HttpSecurity;

    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

    import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;

    import org.springframework.security.config.http.SessionCreationPolicy;

    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

    import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;

    @Configuration

    @EnableWebSecurity

    @EnableGlobalMethodSecurity(prePostEnabled = true)

    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired

    private AuthenticationSuccessHandler successHandler;

    @Autowired

    private UserDetailsServiceImpl userDetailsService;

    @Autowired

    private AuthenticationFailHandler failureHandler;

    @Autowired

    private RestAccessDeniedHandler accessDeniedHandler;

    @Autowired

    private MyFilterSecurityInterceptor myFilterSecurityInterceptor;

    @Override

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

    auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());

    }

    @Override

    protected void configure(HttpSecurity http) throws Exception {

    ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http

    .authorizeRequests();

    //需要有忽略的路径,先放着

    registry.antMatchers("/feng/needlogin").permitAll()

    .antMatchers("/druid/**").permitAll()

    //任何请求都要进行验证

    .and().authorizeRequests().anyRequest().authenticated()

    //关闭csrf和session

    .and().csrf().disable()

    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

    //登录设置登录页和登录处理

    .and().formLogin().loginPage("/feng/needlogin").loginProcessingUrl("/feng/login")

    .permitAll()

    .successHandler(successHandler).failureHandler(failureHandler)

    .and()

    //登出

    .logout().permitAll()

    .and().exceptionHandling().accessDeniedHandler(accessDeniedHandler)

    .and()

    //添加自定义过滤器在FilterSecurityInterceptor之前添加过滤器

    .addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class)

    //添加JWT过滤器,将我们定义的JWT方法加入SpringSecurity的处理流程中

    .addFilter(new JWTAuthenticationFilter(authenticationManager()));

    }

    }/*设置登录处理loginprocessingurl的意思是用户访问这个地址,那么就会调用userdetailservice(在webconfigsecurity中设置的configure)来进行登录的操作。

    JWTAuthenticationFilter是验证jwt带的token是否正常,myFilterSecurityInterceptor是进行权限验证,所以是权限+jwt验证都需要*/

三、其他资料

  1. SpringBoot+SpringSecurity+JWT实RESTfulAPI权限控制: https://blog.csdn.net/linzhiqiang0316/article/details/78358907
  2. Postman解决token传参问题:https://blog.csdn.net/ruanhao1203/article/details/79095629
0