項目結構:

一、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.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.shirojwt</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<!--java_JDK版本-->
<java.version>1.8</java.version>
<!--maven打包插件-->
<maven.plugin.version>3.8.1</maven.plugin.version>
<!--編譯編碼UTF-8-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!--輸出報告編碼UTF-8-->
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!--shiro版本-->
<shiro.version>1.6.0</shiro.version>
<!--jwt版本-->
<java-jwt.version>3.11.0</java-jwt.version>
<!--shiro-redis版本-->
<shiro-redis.version>3.1.0</shiro-redis.version>
<!--json數據格式處理工具-->
<fastjson.version>1.2.75</fastjson.version>
</properties>
<dependencies>
<!--集成springmvc框架並實現自動配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--JWT-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${java-jwt.version}</version>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- shiro-redis -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>${shiro-redis.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--commons類-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
二、 application.yml配置
server:
port: 8000
spring:
redis:
host: 127.0.0.1
port: 6379
password: '918512'
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 500
min-idle: 0
lettuce:
shutdown-timeout: 0
timeout: 2000ms
cache:
type: redis
#自定義屬性
custom:
jwt:
tokenHeader: Authorization
expire_time: 1800000
三、統一返回結果
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class Result {
private static final long serialVersionUID = 1L;
/**
* 后台是否處理成功(狀態)
*/
private boolean state;
/**
* 前后端約定的狀態碼(狀態碼)
*/
private int code;
/**
* 后台響應的信息(處理信息)
*/
private String message;
/**
* 后台響應的數據(返回數據)
*/
private Object data;
public static Result success() {
Result result = new Result();
result.setState(true);
result.setCode(1);
result.setMessage("操作成功");
return result;
}
public static Result success(Object data) {
Result result = new Result();
result.setState(true);
result.setCode(1);
result.setMessage("操作成功");
result.setData(data);
return result;
}
public static Result success(int code, String message) {
Result result = new Result();
result.setState(true);
result.setCode(code);
result.setMessage(message);
return result;
}
public static Result success(int code, String message, Object data) {
Result result = new Result();
result.setState(true);
result.setCode(code);
result.setMessage(message);
result.setData(data);
return result;
}
public static Result fail() {
Result result = new Result();
result.setState(false);
result.setCode(-1);
result.setMessage("操作失敗");
return result;
}
public static Result fail(Object data) {
Result result = new Result();
result.setState(false);
result.setCode(-1);
result.setMessage("操作失敗");
result.setData(data);
return result;
}
public static Result fail(int code, String message) {
Result result = new Result();
result.setState(false);
result.setCode(code);
result.setMessage(message);
return result;
}
public static Result fail(int code, String message, Object data) {
Result result = new Result();
result.setState(false);
result.setCode(code);
result.setMessage(message);
result.setData(data);
return result;
}
}
解決redis存入字符亂碼問題
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/*
* 解決redis存入字符亂碼問題
* */
@Configuration
public class RedisConfigurtion {
@Bean(name = "redisTemplate")
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
四、 JwtUtil 、 JwtFilter 、 JwtToken
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;
/**
* JwtUtil:用來進行簽名和效驗Token
*/
@Slf4j
public class JwtUtil {
/**
* JWT驗證過期時間 EXPIRE_TIME 分鍾
*/
private static final long EXPIRE_TIME = 30 * 60 * 1000;
public static final String SECRET = "zhegelingpaiyoudianchang";
/**
* 生成token簽名EXPIRE_TIME 分鍾后過期
*
* @param username 用戶名(電話號碼)
* @return 加密的token
*/
public static String sign(String username) {
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(SECRET);
// 附帶username信息
String token = JWT.create().withClaim("username", username).withExpiresAt(date).sign(algorithm);
String encodeToken = "";
try {
encodeToken = Base64.getEncoder().encodeToString(token.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
return null;
}
return encodeToken;
}
/**
* 校驗token是否正確
*
* @param token 密鑰
* @return 是否正確
*/
public static boolean verify(String token, String username) {
try {
String decoderToken = new String(Base64.getDecoder().decode(token), StandardCharsets.UTF_8);
//根據密碼生成JWT效驗器
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
//效驗TOKEN
DecodedJWT jwt = verifier.verify(decoderToken);
log.info("登錄驗證成功!");
return true;
} catch (Exception exception) {
log.error("JwtUtil登錄驗證失敗!");
return false;
}
}
/**
* 獲得token中的信息無需secret解密也能獲得
*
* @return token中包含的用戶名
*/
public static String getUsername(String token) {
try {
String decoderToken = new String(Base64.getDecoder().decode(token), StandardCharsets.UTF_8);
DecodedJWT jwt = JWT.decode(decoderToken);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
}
import org.apache.shiro.authc.AuthenticationToken;
/**
* 實現shiro的AuthenticationToken接口的類JwtToken
*/
public class JwtToken implements AuthenticationToken {
private String token;
public JwtToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
import com.alibaba.fastjson.JSONObject;
import com.shirojwt.demo.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* jwt過濾器
*/
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter implements Filter {
/**
* 執行登錄
*
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader("Authorization");
if (token == null) {
return false;
}
JwtToken jwtToken = new JwtToken(token);
// 提交給realm進行登入,如果錯誤他會拋出異常並被捕獲
try {
getSubject(request, response).login(jwtToken);
// 如果沒有拋出異常則代表登入成功,返回true
return true;
} catch (AuthenticationException e) {
return false;
}
}
/**
* 執行登錄認證
*
* @param request
* @param response
* @param mappedValue
* @return
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
try {
return executeLogin(request, response);
} catch (Exception e) {
log.error("JwtFilter過濾驗證失敗!");
return false;
}
}
/**
* 認證失敗時,自定義返回json數據
*
* @param request 請求
* @param response 響應
* @return boolean* @throws Exception 異常
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
Result result = Result.fail(-1, "認證失敗");
Object parse = JSONObject.toJSON(result);
response.setCharacterEncoding("utf-8");
response.getWriter().print(parse);
return super.onAccessDenied(request, response);
}
/**
* 對跨域提供支持
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers",
httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域時會首先發送一個option請求,這里我們給option請求直接返回正常狀態
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
}
五、shiro配置
import com.shirojwt.demo.jwt.JwtToken;
import com.shirojwt.demo.jwt.JwtUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
/**
* shiro自定義認證邏輯
*/
@Component
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private RedisTemplate redisTemplate;
/**
* redis過期時間設置
*/
@Value("${custom.jwt.expire_time}")
private long expireTime;
/**
* 設置對應的token類型
* 必須重寫此方法,不然Shiro會報錯
*
* @param token 令牌
* @return boolean
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
/**
* 授權認證
*
* @param principalCollection 主要收集
* @return {@link AuthorizationInfo}
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//權限認證
System.out.println("開始進行權限認證.............");
//獲取用戶名
String token = (String) SecurityUtils.getSubject().getPrincipal();
String username = JwtUtil.getUsername(token);
//模擬數據庫校驗,寫死用戶名xsy,其他用戶無法登陸成功
if (!"xsy".equals(username)) {
return null;
}
//創建授權信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//創建set集合,存儲權限
HashSet<String> rootSet = new HashSet<>();
//添加權限
rootSet.add("user:show");
rootSet.add("user:admin");
//設置權限
info.setStringPermissions(rootSet);
//返回權限實例
return info;
}
//認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
System.out.println("開始身份認證.....................");
//獲取token
String token = (String) authenticationToken.getCredentials();
//創建字符串,存儲用戶信息
String username = null;
try {
//獲取用戶名
username = JwtUtil.getUsername(token);
} catch (AuthenticationException e) {
throw new AuthenticationException("heard的token拼寫錯誤或者值為空");
}
if (username == null) {
throw new AuthenticationException("token無效");
}
// 校驗token是否超時失效 & 或者賬號密碼是否錯誤
if (!jwtTokenRefresh(token, username, JwtUtil.SECRET)) {
throw new AuthenticationException("Token失效,請重新登錄!");
}
//返回身份認證信息
return new SimpleAuthenticationInfo(token, token, "my_realm");
}
/**
* jwt刷新令牌
*
* @param token 令牌
* @param userName 用戶名
* @param passWord 通過單詞
* @return boolean
*/
public boolean jwtTokenRefresh(String token, String userName, String passWord) {
String redisToken = (String) redisTemplate.opsForValue().get(token);
if (redisToken != null) {
if (!JwtUtil.verify(redisToken, userName)) {
String newToken = JwtUtil.sign(userName);
//設置redis緩存
redisTemplate.opsForValue().set(token, newToken, expireTime * 2 / 1000, TimeUnit.SECONDS);
}
return true;
}
return false;
}
}
import com.shirojwt.demo.jwt.JwtFilter;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.Filter;
@Configuration
public class ShiroConfig {
@Autowired
private ShiroRealm shiroRealm;
/**
* shiro過濾器工廠
* @param securityManager 安全管理器
* @return {@link ShiroFilterFactoryBean}
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(
@Qualifier("securityManager") SecurityManager securityManager) {
//創建攔截鏈實例
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//設置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//設置組登錄請求,其他路徑一律自動跳轉到這里
shiroFilterFactoryBean.setLoginUrl("/login");
//未授權跳轉路徑
shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
//設置攔截鏈map
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//放行請求
filterChainDefinitionMap.put("/shiro/getToken", "anon");
//攔截剩下的其他請求
filterChainDefinitionMap.put("/**", "authc");
//設置攔截規則給shiro的攔截鏈工廠
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
// 添加自己的自定義攔截器並且取名為jwt
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
filterMap.put("jwt", new JwtFilter());
shiroFilterFactoryBean.setFilters(filterMap);
//攔截鏈配置,從上向下順序執行,一般將jwt過濾器放在最為下邊
filterChainDefinitionMap.put("/**", "jwt");
//配置攔截鏈到過濾器工廠
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
//返回實例
return shiroFilterFactoryBean;
}
/**
* 安全管理器
* @return {@link DefaultWebSecurityManager}
*/
@Bean
public DefaultWebSecurityManager securityManager() {
//創建默認的web安全管理器
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
//配置shiro的自定義認證邏輯
defaultSecurityManager.setRealm(shiroRealm);
/*
* 關閉shiro自帶的session,詳情見文檔
* http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
*/
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
defaultSecurityManager.setSubjectDAO(subjectDAO);
//返回安全管理器實例
return defaultSecurityManager;
}
/**
* 開啟Shiro的注解(如@RequiresRoles,@RequiresPermissions)
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
* 開啟aop注解支持
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor
= new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
七、請求
import com.shirojwt.demo.common.Result;
import com.shirojwt.demo.jwt.JwtUtil;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/shiro")
public class ShiroController {
@Autowired
private RedisTemplate redisTemplate;
@Value("${custom.jwt.expire_time}")
private long expireTime;
@RequestMapping("/getToken")
public Result getToken(){
String token = JwtUtil.sign("xsy");
if (token == null) {
return Result.fail(-1, "服務器異常,獲取token失敗");
}
redisTemplate.opsForValue().set(token,token, expireTime*2/100, TimeUnit.SECONDS);
return Result.success(token);
}
@RequiresPermissions("user:admin")
@RequestMapping("/test")
public Result test(){
System.out.println("進入測試,只有帶有令牌才可以進入該方法");
return Result.success(1,"訪問接口成功");
}
}
參照:https://blog.csdn.net/weixin_40736233/article/details/115391934
