简单高效快速整合SpringBoot+SpringSecurity+Jwt实现前后端分离权限框架

一、效果、简叙

首先吐槽一下我以前看到(Security)的文章:

  • 1、有标题没内容(标题很牛逼什么小白、新手都会的);
  • 2、只说核心的流程、处理要求,没有代码(大神你不懂的小白怎么操作,我们不会呀);
  • 3、有图有效果代码拉下来出错 留言不回复;

先给大家看一下效果吧

登录:在这里插入图片描述验证权限:在这里插入图片描述

二、代码编写

首先我先贴出我们Git地址: https://gitee.com/liurunyong/SpringSecurity/tree/master
第一步: 新建项目(此处我就省略了)只把我的POM 贴出来
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.security</groupId>
    <artifactId>spring_security</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring_security</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <mybatis.version>2.1.2</mybatis.version>
        <druid.version>1.1.6</druid.version>
        <jwt.version>1.0.9.RELEASE</jwt.version>
        <jjwt.version>0.9.0</jjwt.version>
        <fastJson.version>1.2.45</fastJson.version>
        <mybatisPlus.version>3.3.1</mybatisPlus.version>
        <common.version>3.5</common.version>
        <swagger.version>2.8.0</swagger.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        <!-- 引入阿里数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- JWT依赖 -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
            <version>${jwt.version}</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>${jjwt.version}</version>
        </dependency>
        <!-- JSON工具 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastJson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatisPlus.version}</version>
        </dependency>
        <!-- StringUtilS工具 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${common.version}</version>
        </dependency>
        <!-- swagger2 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${swagger.version}</version>
        </dependency>
        <!-- swagger-ui -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${swagger.version}</version>
        </dependency>
        <dependency>
            <groupId>com.github.ulisesbocchio</groupId>
            <artifactId>jasypt-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

第二步:创建返回枚举和处理类(ResponseEnum,ServerResponse)
返回枚举(ResponseEnum )
package com.security.common.enmun;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @Description: Response枚举
 * @Author: LiuRunYong
 * @Date: 2020/4/1
 **/

@Getter
@AllArgsConstructor
public enum ResponseEnum {

    /**
     * 响应成功
     */
    SUCCESS(200d, "成功"),
    /**
     * 响应失败
     */
    ERROR(400d, "失败"),
    /**
     * 用户名密码错误
     */
    USERNAME_PASSWORD_ERROR(401.1d, "用户名或密码错误"),
    /**
     * 用户冻结
     */
    USER_LOCK(401.2d, "用户冻结"),
    /**
     * 无对应资源权限
     */
    NO_ACCESS_PERMISSIONS(403d, "无对应资源权限"),
    /**
     * 系统异常
     */
    EXCEPTION(500d, "系统异常"),
    /**
     * 未登录
     */
    NOT_LOGIN(530d, "未登录");

    private final Double code;
    private final String desc;
}

返回处理类(ServerResponse )
package com.security.common.response;


import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.security.common.enmun.ResponseEnum;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletResponse;
import java.io.PrintWriter;
import java.io.Serializable;

/**
 * @Description: 服务返回封装
 * @Author: LiuRunYong
 * * @Date: 2020/4/28
 **/

// 保证序列化json的时候,如果对象为null,则不会转化为json
@JsonInclude(JsonInclude.Include.NON_NULL)
@Getter
@Slf4j
public class ServerResponse<T> implements Serializable {

    private Double status;
    private String msg;
    private String url;
    private T data;

    private ServerResponse(Double status) {
        this.status = status;
    }

    private ServerResponse(Double status, T data) {
        this.status = status;
        this.data = data;
    }

    private ServerResponse(Double status, String msg, T data) {
        this.data = data;
        this.status = status;
        this.msg = msg;
    }

    private ServerResponse(Double status, String msg) {
        this.status = status;
        this.msg = msg;
    }

    private ServerResponse(Double status, String msg, String url) {
        this.status = status;
        this.msg = msg;
        this.url = url;
    }

    // 使该对象不在json序列化中
    @JsonIgnore
    public boolean isSuccess() {
        return this.status.equals(ResponseEnum.SUCCESS.getCode());
    }

    public static <T> ServerResponse<T> createBySuccess() {
        return new ServerResponse<T>(ResponseEnum.SUCCESS.getCode());
    }

    public static <T> ServerResponse<T> createBySuccessMessage(String msg) {
        return new ServerResponse<T>(ResponseEnum.SUCCESS.getCode(), msg);
    }

    public static <T> ServerResponse<T> createBySuccess(T data) {
        return new ServerResponse<T>(ResponseEnum.SUCCESS.getCode(), data);
    }

    public static <T> ServerResponse<T> createByERROR(T data) {
        return new ServerResponse<T>(ResponseEnum.SUCCESS.getCode(), data);
    }

    public static <T> ServerResponse<T> createBySuccess(String msg, T data) {
        return new ServerResponse<T>(ResponseEnum.SUCCESS.getCode(), msg, data);
    }

    public static <T> ServerResponse<T> createByError() {
        return new ServerResponse<T>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getDesc());
    }

    public static <T> ServerResponse<T> createByError(T data) {
        return new ServerResponse<T>(ResponseEnum.ERROR.getCode(), data);
    }

    public static <T> ServerResponse<T> createByErrorMessage(String errorMessage) {
        return new ServerResponse<T>(ResponseEnum.ERROR.getCode(), errorMessage);
    }

    public static <T> ServerResponse<T> createByCodeMessage(Double code, String errorMessage) {
        return new ServerResponse<T>(code, errorMessage);
    }

    /**
     * 无对应资源的权限
     *
     * @return ServerResponse
     */
    public static ServerResponse noAccessPermissions() {
        return new ServerResponse(ResponseEnum.NO_ACCESS_PERMISSIONS.getCode(), ResponseEnum.NO_ACCESS_PERMISSIONS.getDesc());
    }

    /**
     * 未登录
     *
     * @return ServerResponse
     */
    public static ServerResponse notLogin() {
        return new ServerResponse(ResponseEnum.NOT_LOGIN.getCode(), ResponseEnum.NOT_LOGIN.getDesc());
    }

    /**
     * 用户名或密码错误
     *
     * @return ServerResponse
     */
    public static ServerResponse userNameOrPasswordError() {
        return new ServerResponse(ResponseEnum.USERNAME_PASSWORD_ERROR.getCode(), ResponseEnum.USERNAME_PASSWORD_ERROR.getDesc());
    }

    /**
     * 用户冻结
     *
     * @return ServerResponse
     */
    public static ServerResponse userLock() {
        return new ServerResponse(ResponseEnum.USER_LOCK.getCode(), ResponseEnum.USER_LOCK.getDesc());
    }


    /**
     * 使用response输出JSON
     *
     * @param serverResponse 返回响应
     */
    public static void createResponseEnumJson(ServletResponse response, ServerResponse serverResponse) {
        PrintWriter out = null;
        try {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json");
            out = response.getWriter();
            out.println(JSON.toJSONString(serverResponse));
        } catch (Exception e) {
            log.error("【JSON输出异常】" + e);
        } finally {
            if (out != null) {
                out.flush();
                out.close();
            }
        }
    }
}

第三步:编写处理用户登录失败、登录成功、退出、未登录、没权限、处理类
用户登录失败 (UserLoginFailureHandler)
package com.security.common.handler;

import com.security.common.response.ServerResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Description: 登录失败处理类
 * @Author: LiuRunYong
 * @Date: 2020/4/28
 **/
@Slf4j
@Component
public class UserLoginFailureHandler implements AuthenticationFailureHandler {
    /**
     * 登录失败返回结果
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) {
        // 这些对于操作的处理类可以根据不同异常进行不同处理
        if (exception instanceof UsernameNotFoundException) {
            log.info("【登录失败】" + exception.getMessage());
            ServerResponse.createResponseEnumJson(response, ServerResponse.userNameOrPasswordError());
        }
        if (exception instanceof LockedException) {
            log.info("【登录失败】" + exception.getMessage());
            ServerResponse.createResponseEnumJson(response, ServerResponse.userLock());
        }
        if (exception instanceof BadCredentialsException) {
            log.info("【登录失败】" + exception.getMessage());
            ServerResponse.createResponseEnumJson(response, ServerResponse.userNameOrPasswordError());
        }
        ServerResponse.createResponseEnumJson(response, ServerResponse.createByError());
    }
}

用户登录成功(UserLoginSuccessHandler)
package com.security.common.handler;

import com.security.common.jwt.JWTToken;
import com.security.common.response.ServerResponse;
import com.security.common.utils.ResultUtil;
import com.security.model.UserModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description: 登录成功处理类
 * @Author: LiuRunYong
 * @Date: 2020/4/28
 **/
@Slf4j
@Component
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {
    /**
     * 登录成功返回结果
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        // 组装JWT
        UserModel userModel = (UserModel) authentication.getPrincipal();
        ServerResponse.createResponseEnumJson(response, ServerResponse.createBySuccess(JWTToken.createAccessToken(userModel)));
    }
}

用户退出登录 (UserLogoutSuccessHandler)
package com.security.common.handler;

import com.security.common.response.ServerResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Description: 用户登出类
 * @Author: LiuRunYong
 * @Date: 2020/4/28
 **/
@Component
public class UserLogoutSuccessHandler implements LogoutSuccessHandler {
    /**
     * 用户登出返回结果
     * 这里应该让前端清除掉Token
     */
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        SecurityContextHolder.clearContext();
        ServerResponse.createResponseEnumJson(response, ServerResponse.createBySuccessMessage("登出成功"));

    }
}
用户未登录 (UserNotLoginHandler)
package com.security.common.handler;

import com.security.common.response.ServerResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Description: 用户未登录处理类
 * @Author: LiuRunYong
 * @Date: 2020/4/28
 **/
@Component
public class UserNotLoginHandler implements AuthenticationEntryPoint {
    /**
     * 用户未登录返回结果
     */
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) {
        ServerResponse.createResponseEnumJson(response, ServerResponse.notLogin());
    }
}
用户没有权限 (UserNotPermissionHandler)
package com.security.common.handler;

import com.security.common.response.ServerResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Description: 暂无权限处理类
 * @Author: LiuRunYong
 * @Date: 2020/4/28
 **/
@Component
public class UserNotPermissionHandler implements AccessDeniedHandler {
    /**
     * 暂无权限返回结果
     */
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) {
        ServerResponse.createResponseEnumJson(response, ServerResponse.noAccessPermissions());
    }
}
第四步: Token 生成(JWTToken)
package com.security.common.jwt;

import com.alibaba.fastjson.JSON;
import com.security.model.UserModel;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import java.util.Date;

/**
 * @Description: JWT工具类
 * @Author: LiuRunYong
 * @Date: 2020/4/28
 **/
@Slf4j
public class JWTToken {

    /**
     * 生成token
     *
     * @param userModel 自定义的用户对象
     * @return String
     */
    public static String createAccessToken(UserModel userModel) {
        // 登陆成功生成JWT
        String token = Jwts.builder()
                // 放入用户名和用户ID
                .setId(userModel.getUserId() + "")
                // 主题
                .setSubject(userModel.getAccount())
                // 签发时间
                .setIssuedAt(new Date())
                // 签发者
                .setIssuer("LiuRunYong")
                // 自定义属性 放入用户拥有权限
                .claim("authorities", JSON.toJSONString(userModel.getAuthorities()))
                // 失效时间(一天)
                .setExpiration(new Date(System.currentTimeMillis() + 24 * 3600000))
                // 签名算法和密钥
                .signWith(SignatureAlgorithm.HS512, "JWTSecret")
                .compact();
        return token;
    }
}

第五步: Token 过滤器(JWTAuthenticationTokenFilter)
package com.security.common.filter;

import com.alibaba.fastjson.JSONObject;
import com.security.model.UserModel;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.web.authentication.www.BasicAuthenticationFilter;

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.*;

/**
 * @Description: JWT接口请求校验拦截器请求接口时会进入这里验证Token是否合法和过期
 * @Author: LiuRunYong
 * @Date: 2020/4/28
 **/
@Slf4j
public class JWTAuthenticationTokenFilter extends BasicAuthenticationFilter {
    public JWTAuthenticationTokenFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, IOException {
        // 获取请求头中JWT的Token
        String tokenHeader = request.getHeader("Authorization");
        if (null != tokenHeader) {
            try {
                // 解析JWT
                Claims claims = Jwts.parser()
                        .setSigningKey("JWTSecret")
                        .parseClaimsJws(tokenHeader)
                        .getBody();
                // 获取用户名
                String username = claims.getSubject();
                String userId = claims.getId();
                if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(userId)) {
                    // 获取角色
                    Set<GrantedAuthority> authorities = new HashSet<>();
                    String authority = claims.get("authorities").toString();
                    if (!StringUtils.isEmpty(authority)) {
                        List<Map<String, String>> authorityMap = JSONObject.parseObject(authority, List.class);
                        authorityMap.forEach(
                                role -> authorities.add(new SimpleGrantedAuthority(role.get("authority")))
                        );
                    }
                    //组装参数
                    UserModel userModel = new UserModel();
                    userModel.setUserName(claims.getSubject()).setUserId(Integer.parseInt(claims.getId())).setAuthorities(authorities);
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userModel, userId, authorities);
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            } catch (ExpiredJwtException e) {
                log.info("Token过期");
            } catch (Exception e) {
                log.info("Token无效");
            }
        }
        filterChain.doFilter(request, response);
        return;
    }
}

第六步: 自定义权限实现类(UserPermissionEvaluator)
package com.security.common.evaluator;

import com.security.model.PermissionModel;
import com.security.model.UserModel;
import com.security.service.UserService;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @Description: 自定义权限注解验证
 * @Author: LiuRunYong
 * @Date: 2020/4/28
 **/

@Component
public class UserPermissionEvaluator implements PermissionEvaluator {


    @Resource
    UserService userService;

    public UserPermissionEvaluator() {
    }

    /**
     * hasPermission鉴权方法
     * 这里仅仅判断PreAuthorize注解中的permission
     * 实际中可以根据业务需求设计数据库通过targetUrl和permission做更复杂鉴权
     *
     * @param authentication 用户身份
     * @param targetUrl      请求路径
     * @param permission     请求路径权限
     * @return boolean 是否通过
     */
    @Override
    public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {
        // 获取用户信息
        UserModel userModel = (UserModel) authentication.getPrincipal();
        // 获取用户对应角色的权限(因为SQL中已经GROUP BY了,所以返回的list是不重复的)
        List<PermissionModel> permissionModels = userService.selectUserModelByUserName(userModel.getUserName()).getPermissionModels();
        List<String> rolePermissions = new ArrayList<>();
        for (PermissionModel permissionModel : permissionModels) {
            rolePermissions.add(permissionModel.getPermissionValue());
        }
        // 权限对比
        return rolePermissions.contains(permission.toString());
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        return false;
    }
}

第七步:自定义登录实现(UserLoginProvider)
package com.security.common.provider;

import com.security.model.RoleModel;
import com.security.model.UserModel;
import com.security.service.UserService;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @Description: 自定义登录验证
 * @Author: LiuRunYong
 * @Date: 2020/4/28
 **/

@Component
public class UserLoginProvider implements AuthenticationProvider {

    final UserService userService;

    public UserLoginProvider(UserService userService) {
        this.userService = userService;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 获取表单输入中返回的用户名
        String userName = (String) authentication.getPrincipal();
        // 获取表单中输入的密码
        String password = (String) authentication.getCredentials();
        // 查询用户是否存在
        UserModel userModel = userService.selectUserModelByUserName(userName);
        if (userModel == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        // 我们还要判断密码是否正确,这里我们的密码使用BCryptPasswordEncoder进行加密的
        if (!new BCryptPasswordEncoder().matches(password, userModel.getPassword())) {
            throw new BadCredentialsException("密码不正确");
        }
        // 还可以加一些其他信息的判断,比如用户账号已停用等判断
        if (userModel.getState() != 0) {
            throw new LockedException("该用户已被冻结");
        }
        // 角色集合
        Set<GrantedAuthority> authorities = new HashSet<>();
        // 查询用户角色
        List<RoleModel> roleModels = userModel.getRoleModels();
        // 循环添加角色信息
        for (RoleModel roleModel : roleModels) authorities.add(new SimpleGrantedAuthority(roleModel.getRoleName()));
        userModel.setAuthorities(authorities);
        // 进行登录
        return new UsernamePasswordAuthenticationToken(userModel, password, authorities);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return true;
    }
}
第八步: 最重要的(SecurityConfig)
package com.security.common.config;

import com.security.common.evaluator.UserPermissionEvaluator;
import com.security.common.filter.JWTAuthenticationTokenFilter;
import com.security.common.handler.*;
import com.security.common.provider.UserLoginProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
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.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;

/**
 * @Description:
 * @Author: LiuRunYong
 * @Date: 2020/4/28
 **/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启权限注解,默认是关闭的
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    static String[] EXCLUDEPATH = {"/index", "/login/**", "/v2/api-docs", "/swagger-ui.html", "/swagger-resources/**", "/webjars/**"};
    /**
     * 自定义登录成功处理器
     */
    final UserLoginSuccessHandler userLoginSuccessHandler;
    /**
     * 自定义登录失败处理器
     */
    final UserLoginFailureHandler userLoginFailureHandler;
    /**
     * 自定义注销成功处理器
     */
    final UserLogoutSuccessHandler userLogoutSuccessHandler;
    /**
     * 自定义暂无权限处理器
     */
    final UserNotPermissionHandler userNotPermissionHandler;
    /**
     * 自定义未登录的处理器
     */
    final UserNotLoginHandler userNotLoginHandler;
    /**
     * 自定义登录逻辑验证器
     */
    final UserLoginProvider userLoginProvider;

    public SecurityConfig(UserLoginSuccessHandler userLoginSuccessHandler,
                          UserLoginFailureHandler userLoginFailureHandler,
                          UserLogoutSuccessHandler userLogoutSuccessHandler,
                          UserNotPermissionHandler userNotPermissionHandler,
                          UserNotLoginHandler userNotLoginHandler,
                          UserLoginProvider userLoginProvider) {
        this.userLoginSuccessHandler = userLoginSuccessHandler;
        this.userLoginFailureHandler = userLoginFailureHandler;
        this.userLogoutSuccessHandler = userLogoutSuccessHandler;
        this.userNotPermissionHandler = userNotPermissionHandler;
        this.userNotLoginHandler = userNotLoginHandler;
        this.userLoginProvider = userLoginProvider;
    }

    /**
     * 加密方式
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 注入自定义PermissionEvaluator
     */
    @Bean
    public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler() {
        DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
        handler.setPermissionEvaluator(new UserPermissionEvaluator());
        return handler;
    }

    /**
     * 配置登录验证逻辑
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        // 这里可启用我们自己的登陆验证逻辑
        auth.authenticationProvider(userLoginProvider);
    }

    /**
     * 配置security的控制逻辑
     *
     * @param http 请求
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 从配置文件获取不用进行权限验证的请求或资源
                .antMatchers(EXCLUDEPATH).permitAll()
                // 其他的需要登陆后才能访问
                .anyRequest().authenticated()
                .and()
                // 配置未登录自定义处理类
                .httpBasic().authenticationEntryPoint(userNotLoginHandler)
                .and()
                // 配置登录地址
                .formLogin()
                .loginProcessingUrl("/login/userLogin")
                // 配置登录成功自定义处理类
                .successHandler(userLoginSuccessHandler)
                // 配置登录失败自定义处理类
                .failureHandler(userLoginFailureHandler)
                .and()
                // 配置退出地址
                .logout()
                .logoutUrl("/login/userLogout")
                // 配置用户登出自定义处理类
                .logoutSuccessHandler(userLogoutSuccessHandler)
                .and()
                // 配置没有权限自定义处理类
                .exceptionHandling().accessDeniedHandler(userNotPermissionHandler)
                .and()
                // 开启跨域
                .cors()
                .and()
                // 取消跨站请求伪造防护
                .csrf().disable();
        // 基于Token不需要session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 禁用缓存
        http.headers().cacheControl();
        // 添加JWT过滤器
        http.addFilter(new JWTAuthenticationTokenFilter(authenticationManager()));
    }
}
第九步: 配置实体Bean(PermissionModel、RoleModel、UserModel) 其他的我暂时没用到
PermissionModel
package com.security.model;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * @Description: PermissionModel
 * @Author: LiuRunYong
 * @Date: 2020/4/28
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@ApiModel(description = "权限对象Bean")
public class PermissionModel implements Serializable {

    @ApiModelProperty(value = "用户主键(新增?,更新*)")
    private Integer permissionId;

    @ApiModelProperty(value = "权限名称")
    private String permissionName;

    @ApiModelProperty(value = "权限值")
    private String permissionValue;

    @ApiModelProperty(value = "权限类型(0:目录,1:菜单,2:按钮)")
    private String permissionType;

    @ApiModelProperty(value = "权限状态(0:可用,1:不可用)")
    private String permissionState;

    @ApiModelProperty(value = "上级编号")
    private String superiorId;

    @ApiModelProperty(value = "创建时间")
    private String createTime;

    @ApiModelProperty(value = "更新时间")
    private String updateTime;
}
RoleModel
package com.security.model;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.List;

/**
 * @Description: RoleModel
 * @Author: LiuRunYong
 * @Date: 2020/4/28
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@ApiModel(description = "角色对象Bean")
public class RoleModel implements Serializable {

    @ApiModelProperty(value = "主键(新增?,更新*)")
    private Integer roleId;

    @ApiModelProperty(value = "角色名称")
    @TableField()
    private String roleName;

    @ApiModelProperty(value = "角色标题")
    private String roleTitle;

    @ApiModelProperty(value = "状态(0:可用,1:不可用)")
    private String state;

    @ApiModelProperty(value = "描述", hidden = true)
    private String description;

    @ApiModelProperty(value = "创建时间")
    private String createTime;

    @ApiModelProperty(value = "更新时间", hidden = true)
    private String updateTime;
}

UserModel
package com.security.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.springframework.security.core.GrantedAuthority;

import java.io.Serializable;
import java.util.List;
import java.util.Set;

/**
 * @Description: UserModel
 * @Author: LiuRunYong
 * @Date: 2020/4/28
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@ApiModel(description = "用户Bean")
@ToString(exclude = "password")
public class UserModel implements Serializable {

    @ApiModelProperty(value = "主键(新增?,更新*)")
    private Integer userId;

    @ApiModelProperty(value = "账号", hidden = true)
    private String account;

    @ApiModelProperty(value = "用户名称")
    private String userName;

    @ApiModelProperty(value = "密码", hidden = true)
    @JsonIgnore
    private String password;

    @ApiModelProperty(value = "手机号")
    private String phone;

    @ApiModelProperty(value = "邮箱")
    private String email;

    @ApiModelProperty(value = "性别(0:男,1:女)")
    private Integer sex;

    @ApiModelProperty(value = "用户身份证号")
    private Integer idCard;

    @ApiModelProperty(value = "用户状态(0:可用,1:不可用,2:暂时锁定)")
    private Integer state;

    @ApiModelProperty(value = "创建时间", hidden = true)
    private String createTime;

    @ApiModelProperty(value = "修改时间", hidden = true)
    private String updateTime;

    @ApiModelProperty(value = "角色", hidden = true)
    private Set<GrantedAuthority> authorities;

    @ApiModelProperty(value = "用户角色", hidden = true)
    private List<RoleModel> roleModels;

    @ApiModelProperty(value = "用户权限", hidden = true)
    private List<PermissionModel> permissionModels;
}
第十步: 配置实体Dao (UserMapper)
UserMapper
package com.security.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.security.model.UserModel;

/**
 * @Description: UserMapper
 * @Author: LiuRunYong
 * @Date: 2020/4/28
 **/
public interface UserMapper extends BaseMapper<UserModel> {

    /**
     * 根据用户名查询用户
     *
     * @param userName 用户名称
     * @return UserModel
     */
    UserModel selectUserModelByUserName(String userName);

}
第十一步: 配置Service(UserService)
UserService
package com.security.service;

import com.security.model.UserModel;

/**
 * @Description:
 * @Author: LiuRunYong
 * @Date: 2020/4/28
 **/
public interface UserService {

    /**
     * 根据用户名称查询用户信息
     *
     * @param userName 用户名称
     * @return UserModel
     */
    UserModel selectUserModelByUserName(String userName);
}
第十二步: 配置serviceImpl(UserServiceImpl)
UserServiceImpl
package com.security.impl;

import com.security.dao.UserMapper;
import com.security.model.PermissionModel;
import com.security.model.RoleModel;
import com.security.model.UserModel;
import com.security.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * @Description:
 * @Author: LiuRunYong
 * @Date: 2020/4/28
 **/
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl implements UserService {

    final UserMapper userMapper;

    public UserServiceImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public UserModel selectUserModelByUserName(String userName) {
        return userMapper.selectUserModelByUserName(userName);
    }
}

第十三步: 配置controller(UserController)
UserController
package com.security.controller;

import com.security.model.UserModel;
import com.security.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description:
 * @Author: LiuRunYong
 * @Date: 2020/4/28
 **/
@RestController
@RequestMapping("/user/")
@Api(tags = "用户模块")
public class UserController {

    final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping(value = "list")
    @PreAuthorize("hasPermission(null ,'system_manage')")
    @ApiOperation(value = "用户列表")
    public UserModel userList() {
        return userService.selectUserModelByUserName("187123456789");
    }
}

最后一步 启动类、swagger2配置类
SpringSecurityApplication
package com.security;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.security.dao")
public class SpringSecurityApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityApplication.class, args);
    }

}
Swagger2
package com.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * @Description: Swagger2配置
 * @Author: LiuRunYong
 * @Date: 2020/4/29
 **/
@Configuration
@EnableSwagger2
public class Swagger2 {

    /**
     * swagger2的配置文件
     */
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.security.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    /**
     * 构建 api文档的详细信息函数,注意这里的注解引用的是哪个
     */
    private ApiInfo apiInfo() {
        // 获取工程名称
        String projectName = System.getProperty("user.dir");
        return new ApiInfoBuilder()
                .title(projectName.substring(projectName.lastIndexOf("\\") + 1) + " API接口文档")
                .contact(new Contact("Liurunyong", "http://www.baidu.com", "*********@qq.com"))
                .version("1.0")
                .description("API文档")
                .build();
    }
}
贴出我们application.yml
# 服务端口
server:
  port: 10713

# 解密工具
jasypt:
  encryptor:
    password: EWRREWRERWECCCXC
    algorithm: PBEWithMD5AndDES

# 日志输出
logging:
  level:
    com.security.dao: debug

# 数据库相关配置
spring:
  application:
    name: cloud-provider-payment
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:10224/security?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: ENC(u1XJHLCnjmjlBvIpF7mA1g==)
    password: ENC(qtBFnHfnGN8ew58eFcm6bDvmz45bKPsO)

最后我们启动项目访问:

在这里插入图片描述

下来项目除了**“EXCLUDEPATH”、请求头中不要“Authorization"** 其他的请求必须添加(这是重点)在这里插入图片描述

至此SpringBoot+SpringSecurity+jwt 整合完成,如果那些地方不对,请路过的大神指正。

  • 7
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值