簡單高效快速整合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 整合完成,如果那些地方不對,請路過的大神指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章