使用Token进行登录认证功能;用户登录后获取Token,调用固定规则接口需要在请求头中传入Token才可调用接口否则提示相应异常
<!-- cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-ehcache -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.9.1</version>
</dependency>
<!-- jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
Shiro配置代码,代码如下:
import com.example.whwechat.oauth2.OAuth2Filter;
import com.example.whwechat.oauth2.OAuth2Realm;
import net.sf.ehcache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Shiro配置
*
* @author chenshun
* @date 2017-04-20 18:33
*/
@Configuration
public class ShiroConfig {
@Bean("sessionManager")
public SessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionIdCookieEnabled(true);
return sessionManager;
}
@Bean("securityManager")
public SecurityManager securityManager(OAuth2Realm oAuth2Realm, SessionManager sessionManager, EhCacheManager cacheManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//启用身份验证缓存,即缓存AuthenticationInfo信息,默认false
oAuth2Realm.setAuthenticationCachingEnabled(true);
// 对应encachexml缓存配置
oAuth2Realm.setAuthenticationCacheName("defaultCache");
//启用授权缓存,即缓存AuthorizationInfo信息,默认false
oAuth2Realm.setAuthorizationCachingEnabled(true);
// 对应encachexml缓存配置
oAuth2Realm.setAuthorizationCacheName("defaultCache");
securityManager.setRealm(oAuth2Realm);
securityManager.setSessionManager(sessionManager);
securityManager.setCacheManager(cacheManager);
//关闭shiro自带的session
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
return securityManager;
}
@Bean
public EhCacheManager ehCacheManager(CacheManager cacheManager) {
EhCacheManager em = new EhCacheManager();
//将ehcacheManager转换成shiro包装后的ehcacheManager对象
em.setCacheManager(cacheManager);
em.setCacheManagerConfigFile("classpath:ehcache.xml");
return em;
}
@Bean("shiroFilter")
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
//oauth过滤
Map<String, Filter> filters = new HashMap<>();
filters.put("oauth2", new OAuth2Filter());
shiroFilter.setFilters(filters);
//注意此处使用的是LinkedHashMap,是有顺序的,shiro会按从上到下的顺序匹配验证,匹配了就不再继续验证
//所有上面的url要苛刻,宽松的url要放在下面,尤其是"/**"要放到最下面,如果放前面的话其后的验证规则就没作用了。
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/token", "anon");
// 除上以外所有url前缀带有Interface都需要通过验证,否则直接访问登陆界面
//我这里设置的是auth后缀的地址进行权限校验,也可以根据自己需求设置
filterChainDefinitionMap.put("/auth/**", "oauth2");
filterChainDefinitionMap.put("/", "anon");
shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilter;
}
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
oauth2配置类
代码如下:
import com.alibaba.fastjson.JSONObject;
import com.example.whcommon.util.sm2.StringUtil;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* oauth2过滤器
*
* @author Administrator
*/
public class OAuth2Filter extends AuthenticatingFilter {
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token
String token = getRequestToken((HttpServletRequest) request);
if (StringUtil.isEmpty(token)) {
return null;
}
return new OAuth2Token(token);
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return ((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name());
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token,如果token不存在,直接返回401
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = getRequestToken((HttpServletRequest) request);
if (StringUtil.isEmpty(token)) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));
JSONObject json = new JSONObject();
json.put("code", "401");
json.put("message", "invalid token");
httpResponse.getWriter().print(json);
return false;
}
return executeLogin(request, response);
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));
try {
//处理登录失败的异常
Throwable throwable = e.getCause() == null ? e : e.getCause();
JSONObject json = new JSONObject();
json.put("code", "401");
json.put("message", throwable.getMessage());
httpResponse.getWriter().print(json);
} catch (IOException e1) {
}
return false;
}
/**
* 获取请求的token
*/
private String getRequestToken(HttpServletRequest httpRequest) {
//从header中获取token
String token = httpRequest.getHeader("token");
//如果header中不存在token,则从参数中获取token
if (StringUtil.isEmpty(token)) {
token = httpRequest.getParameter("token");
}
return token;
}
}
代码如下:
import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.whwechat.dao.PermissionsDao;
import com.example.whwechat.util.TokenGenerator;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 认证
*/
@Component
public class OAuth2Realm extends AuthorizingRealm {
//这个查用户信息的,根据自己代码修改
@Resource
@Lazy
PermissionsDao permissionsDao;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof OAuth2Token;
}
/**
* 认证(登录时调用)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
System.out.println("认证(登录时调用)");
String accessToken = (String) token.getPrincipal();
DecodedJWT decodedJwt = TokenGenerator.verifyManagerToken(accessToken);
if (decodedJwt == null) {
throw new IncorrectCredentialsException("登录已过期,请重新登录");
}
//TokenGenerator 这个工具类写在下面
String code = TokenGenerator.getManagerCode(decodedJwt);
//查询用户密码,根据实际需求修改
String userPassword = permissionsDao.getPassword(code);
if (userPassword == null) {
throw new UnknownAccountException("用户不存在");
}
return new SimpleAuthenticationInfo(userPassword, accessToken, getName());
}
/**
* 自定义方法:清除所有 授权缓存
*/
public void clearAllCachedAuthorizationInfo() {
getAuthorizationCache().clear();
}
/**
* 自定义方法:清除所有 认证缓存
*/
public void clearAllCachedAuthenticationInfo() {
getAuthenticationCache().clear();
}
/**
* 自定义方法:清除所有的 认证缓存 和 授权缓存
*/
public void clearAllCache() {
clearAllCachedAuthenticationInfo();
clearAllCachedAuthorizationInfo();
}
public static void main(String[] args) {
// 其他接口调用清除缓存demo
DefaultWebSecurityManager securityManager =
(DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
OAuth2Realm oAuth2Realm = (OAuth2Realm) securityManager.getRealms().iterator().next();
oAuth2Realm.clearAllCache();
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("这是权限信息");
return null;
}
代码如下:
import org.apache.shiro.authc.AuthenticationToken;
/**
* token
*
* @author Administrator
*/
public class OAuth2Token implements AuthenticationToken {
private final String token;
public OAuth2Token(String token) {
this.token = token;
}
@Override
public String getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
代码如下:
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @author wcs
* @description token 用户信息AES加密工具类
* @date 2021/9/1 9:46
*/
@Component
public class TokenAesUtil {
/**
* token用户信息AES加密密钥
*/
private static final String AES_USER_INFO_SECRET_KEY = "dolNypS7YGV5fzZm";
SymmetricCrypto aes;
/**
* 初始化AES对象
*/
private void initAes() {
aes = new SymmetricCrypto(SymmetricAlgorithm.AES, AES_USER_INFO_SECRET_KEY.getBytes());
}
/**
* 解密只返回用户id
*
* @param encryptHex 加密串
* @return 用户id
*/
public String decryptUserId(String encryptHex) {
String userStr = decryptStr(encryptHex);
JSONObject jsonObject = JSONObject.parseObject(userStr);
return jsonObject.getString("userId");
}
/**
* 解密只返回用户证件号
*
* @param encryptHex 加密串
* @return 用户证件号
*/
public String decryptCertNo(String encryptHex) {
String userStr = decryptStr(encryptHex);
JSONObject jsonObject = JSONObject.parseObject(userStr);
return jsonObject.getString("certNo");
}
/**
* 判断用户密码强度
*
* @param password 用户密码
* @return true:弱密码,false:强密码
*/
public static Boolean isStringPwd(String password) {
Map<String, String> map = new HashMap<String, String>();
for (int i = 0; i < password.length(); i++) {
int a = password.charAt(i);
if (a >= 48 && a <= 57) {// 数字
map.put("数字", "数字");
} else if (a >= 65 && a <= 90) {// 大写
map.put("大写", "大写");
} else if (a >= 97 && a <= 122) {// 小写
map.put("小写", "小写");
} else {
map.put("特殊", "特殊");
}
}
Set<String> sets = map.keySet();
int pwdSize = sets.size();// 密码字符种类数
int pwdLength = password.length();// 密码长度
return pwdSize < 3 || pwdLength < 8;
}
/**
* 解密只返回用户真实姓名
*
* @param encryptHex 加密串
* @return 用户真实姓名
*/
public String decryptRealName(String encryptHex) {
String userStr = decryptStr(encryptHex);
JSONObject jsonObject = JSONObject.parseObject(userStr);
return jsonObject.getString("realName");
}
/**
* 解密只返回用户手机号
*
* @param encryptHex 加密串
* @return 用户手机号
*/
public String decryptTelephone(String encryptHex) {
String userStr = decryptStr(encryptHex);
JSONObject jsonObject = JSONObject.parseObject(userStr);
return jsonObject.getString("telephone");
}
public String decryptHealthCard(String encryptHex) {
String userStr = decryptStr(encryptHex);
JSONObject jsonObject = JSONObject.parseObject(userStr);
return jsonObject.getString("healthCard");
}
/**
* 解密返回用户信息JSON
*
* @param encryptHex 加密串
* @return 用户信息JSON
*/
public JSONObject decryptUserJson(String encryptHex) {
String userStr = decryptStr(encryptHex);
return JSONObject.parseObject(userStr);
}
/**
* @param content 需要加密的内容
* @return 加密为16进制表示
*/
public String encryptHex(String content) {
if (aes == null) {
initAes();
}
return aes.encryptHex(content);
}
/**
* 解密
*
* @param encryptHex 加密串
* @return 解密数据
*/
public String decryptStr(String encryptHex) {
if (aes == null) {
initAes();
}
return aes.decryptStr(encryptHex, CharsetUtil.CHARSET_UTF_8);
}
}
代码如下:
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.whcommon.util.MyStringUtil;
import com.example.whcommon.util.sm2.StringUtil;
import com.example.whwechat.exception.CustomException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Calendar;
import java.util.Date;
/**
* 生成token
*
* @author Administrator
*/
@Component
@Slf4j
public class TokenGenerator {
@Resource
private TokenAesUtil tokenAesUtil;
@Resource
protected HttpServletRequest request; // 自动注入request
/**
* 用户
*/
private final int USER = 0;
/**
* 管理员用户
*/
private final int MANAGER_USER = 1;
/**
* 亲情用户
*/
private final int RELATIVE_USER = 2;
public static final String JWT_SECRET = "TESTSIESST";
private static final String JWT_ISS = "测试信息技术有限公司";
public static final String JWT_USER_SECRET = "D2M0R2H1";
private static int managerExpiryTime = 1;
private static int managerExpiryTimeUnit = Calendar.HOUR;
private static int userExpiryTime = 7;
private static int userExpiryTimeUnit = Calendar.DATE;
private static int relativeUserExpiryTime = 9999;
private static int relativeUserExpiryTimeUnit = Calendar.DATE;
/**
* 设置token过期时间
*/
private void setTokenExpireTime(int type, String configValue) {
//系统Token过期时间
String[] split = configValue.split("\\|");
int expireTime = Integer.parseInt(split[0]);
if (type == MANAGER_USER) {
managerExpiryTime = expireTime;
} else if (type == USER) {
userExpiryTime = expireTime;
} else {
relativeUserExpiryTime = expireTime;
}
String timeUnit = split[1];
if ("h".equalsIgnoreCase(timeUnit)) { // 小时
if (type == MANAGER_USER) {
managerExpiryTimeUnit = Calendar.HOUR;
} else if (type == USER) {
userExpiryTimeUnit = Calendar.HOUR;
} else {
relativeUserExpiryTimeUnit = Calendar.HOUR;
}
} else if ("m".equalsIgnoreCase(timeUnit)) { // 分钟
if (type == MANAGER_USER) {
managerExpiryTimeUnit = Calendar.MINUTE;
} else if (type == USER) {
userExpiryTimeUnit = Calendar.MINUTE;
} else {
relativeUserExpiryTimeUnit = Calendar.MINUTE;
}
} else { // 月
if (type == MANAGER_USER) {
managerExpiryTimeUnit = Calendar.DATE;
} else if (type == USER) {
userExpiryTimeUnit = Calendar.DATE;
} else {
relativeUserExpiryTimeUnit = Calendar.DATE;
}
}
}
/**
* 获取token
*
* @param userName 用户名称
* @param configValue SYSTEM_USER_TOKEN_TIME (Token过期时间) 90|d
* @return token
*/
public String generateManagerToken(String userName, String configValue) {
try {
setTokenExpireTime(MANAGER_USER, configValue);
Algorithm algorithm = Algorithm.HMAC256(JWT_SECRET);
Calendar calendar = Calendar.getInstance();
Date currentDate = calendar.getTime();
// 设置token过期时间
calendar.add(managerExpiryTimeUnit, managerExpiryTime);
return JWT.create()
.withIssuer(JWT_ISS)
.withExpiresAt(calendar.getTime())
.withClaim("userName", userName)
.withIssuedAt(currentDate)
.sign(algorithm);
} catch (JWTCreationException exception) {
// Invalid Signing configuration / Couldn't convert Claims.
throw new JWTCreationException(exception.getMessage(), exception.getCause());
}
}
public static DecodedJWT verifyManagerToken(String token) {
return verifyToken(token, JWT_SECRET);
}
public static DecodedJWT verifyUserToken(String token) {
// TODO 为方便开发,使用相同的密钥加密,线上环境需修改
return verifyToken(token, JWT_SECRET);
//return verifyToken(token, JWT_USER_SECRET);
}
/**
* 校验token
*
* @param token token
* @return 校验通过返回DecodedJWT信息,失败返回null
*/
private static DecodedJWT verifyToken(String token, String secret) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier =
JWT.require(algorithm)
.withIssuer(JWT_ISS)
.build(); // Reusable verifier instance
return verifier.verify(token);
} catch (JWTVerificationException exception) {
// Invalid signature/claims
return null;
}
}
/**
* 获取用户姓名
*
* @return 用户姓名
*/
public String getUserRealName() {
String token = getRequestToken();
String userStr = decodeUser(token);
return tokenAesUtil.decryptRealName(userStr);
}
/**
* 获取用户信息JSON
*
* @return JSONObject
*/
public JSONObject getUserJson() {
String token = getRequestToken();
String userStr = decodeUser(token);
return tokenAesUtil.decryptUserJson(userStr);
}
private String decodeUser(String token) {
if (MyStringUtil.isEmpty(token)) {
log.info("token为空");
return null;
}
DecodedJWT jwt = verifyUserToken(token);
if (jwt == null) {
log.info("非法token:" + token);
return null;
}
String strUser = jwt.getClaim("user").asString();
if (MyStringUtil.isEmpty(strUser)) {
log.error("token用户信息为空:" + token);
throw new CustomException("非法token,user is null");
}
return strUser;
}
/**
* 根据token获取code账号
*
* @param decodedJwt decodedJwt
* @return code
*/
public static String getManagerCode(DecodedJWT decodedJwt) {
return decodedJwt.getClaim("userName").asString();
}
/**
* 根据token获取用户信息
*
* @param token token
* @return ManagerUser
*/
public static String getManagerUser(String token) {
if (MyStringUtil.isEmpty(token)) {
return null;
}
DecodedJWT jwt = verifyManagerToken(token);
if (jwt == null) {
return null;
}
return builderManagerUser(jwt);
}
private static String builderManagerUser(DecodedJWT jwt) {
return jwt.getClaim("userName").asString();
}
/**
* 获取请求的token
*/
private String getRequestToken() {
String rToken = request.getHeader("rToken");
if (StringUtil.isNotEmpty(rToken)) {
return rToken;
}
// 从header中获取token
String token = request.getHeader("token");
// 如果header中不存在token,则从参数中获取token
if (MyStringUtil.isEmpty(token)) {
token = request.getParameter("token");
}
log.info("请求token:" + token);
return token;
}
}
接下来就比较简单了,代码里面直接调用工具类将Token返回给前端;
代码如下:
//获取Token 90|d Token的有效时间
String token = tokenGenerator.generateManagerToken(username, "90|d");
JSONObject jsonObject = new JSONObject();
jsonObject.put("token", token);
然后前端调用的时候请求头带上Token即可,下面的是PostMan请求示例:
这就是Token校验的整个过程以及相关的代码了;