一、效果、簡敘
首先吐槽一下我以前看到(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"** 其他的請求必須添加(這是重點)