0. 导入依赖
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis-spring-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
配置yml
jwt:
secret: xxxxxx
expiration: 86400000
tokenHeader: Authorization
shiro-redis:
redis-manager:
host: xxxxxx
database: xxxxxxxxxx
1. ShiroConfig
- 配置安全管理器
- 配置Session管理器
- 配置缓存
- 配置过滤路径
package com.yang.server.config.shiro;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SessionStorageEvaluator;
import org.apache.shiro.mgt.SubjectDAO;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSessionStorageEvaluator;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisSessionDAO;
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;
@Configuration
public class ShiroConfig {
/**
* shiro过滤器配置
*
* @param defaultWebSecurityManager
* @param shiroFilterChainDefinition
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager,
ShiroFilterChainDefinition shiroFilterChainDefinition,
JwtFilter jwtFilter) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
// 设置过滤器
Map<String, Filter> filter = new HashMap<>();
filter.put("jwt", jwtFilter);
shiroFilterFactoryBean.setFilters(filter);
// 设置过滤路径
shiroFilterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());
return shiroFilterFactoryBean;
}
/**
* 过滤路径配置
*
* @return
*/
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();
LinkedHashMap<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/doc.html", "anon");
filterMap.put("/admin", "jwt");
filterMap.put("/login/info", "jwt");
defaultShiroFilterChainDefinition.addPathDefinitions(filterMap);
return defaultShiroFilterChainDefinition;
}
/**
* 安全管理器
*
* @return
*/
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(AccountRealm accountRealm,
RedisCacheManager redisCacheManager,
DefaultWebSessionManager defaultWebSessionManager) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
// 设置realm
defaultWebSecurityManager.setRealm(accountRealm);
// 设置redis缓存
defaultWebSecurityManager.setCacheManager(redisCacheManager);
// 设置session管理器
// 关闭session
DefaultWebSessionStorageEvaluator defaultWebSessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
defaultWebSessionStorageEvaluator.setSessionStorageEnabled(false);
DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
defaultSubjectDAO.setSessionStorageEvaluator(defaultWebSessionStorageEvaluator);
defaultWebSecurityManager.setSubjectDAO(defaultSubjectDAO);
// defaultWebSecurityManager.setSessionManager(defaultWebSessionManager);
return defaultWebSecurityManager;
}
/**
* redsi session管理
*
* @param redisSessionDAO
* @return
*/
@Bean
public DefaultWebSessionManager defaultWebSessionManager(RedisSessionDAO redisSessionDAO) {
DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
// 设置redis session管理
defaultWebSessionManager.setSessionDAO(redisSessionDAO);
return defaultWebSessionManager;
}
/**
* 定制realm注入
*
* @return
*/
@Bean
public AccountRealm getRealm() {
AccountRealm accountRealm = new AccountRealm();
// 开启验证缓存
accountRealm.setAuthenticationCachingEnabled(true);
// 开启授权缓存
accountRealm.setAuthorizationCachingEnabled(true);
return accountRealm;
}
}
2. JwtFilter
自定义过滤器,重写过滤规则
package com.yang.server.config.shiro;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Strings;
import com.yang.server.common.lang.Result;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.stereotype.Component;
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;
import java.io.PrintWriter;
@Component
public class JwtFilter extends AuthenticatingFilter {
/**
* 从请求中获取token
* @param servletRequest
* @param servletResponse
* @return
* @throws Exception
*/
@Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
return getToken(servletRequest, servletResponse);
}
/**
* 请求首先进入preHandle中,我们进行跨域请求授权
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
HttpServletResponse httpServletResponse = WebUtils.toHttp(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"));
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name()))
{
httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
/**
* 访问被允许,请求过来后首先走这里,若是通过,直接放行,否则走onAccessDenied;
* 此处一般放行特殊请求,一般进行跨域option请求处理
* @param request
* @param response
* @param mappedValue
* @return
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return super.isAccessAllowed(request, response, mappedValue);
}
/**
* isAccessAllowed未通过走这里,这里一般需要进行验证,走realm进行验证
* @param servletRequest
* @param servletResponse
* @return
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
return executeLogin(servletRequest, servletResponse);
}
/**
* 登录成功后处理
* @param token
* @param subject
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
return super.onLoginSuccess(token, subject, request, response);
}
/**
* 登录失败后处理
* @param token
* @param e
* @param request
* @param response
* @return
*/
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
// 捕获登录过程中的异常
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
Throwable throwable = e.getCause() == null ? e : e.getCause();
Result result = Result.error(throwable.getLocalizedMessage());
String s = JSON.toJSONString(result);
try {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
writer.print(s);
} catch (IOException ioException) {
ioException.printStackTrace();
}
return false;
}
/**
* 工具方法,获取请求头中的token
* @param servletRequest
* @param servletResponse
* @return
*/
protected JwtToken getToken(ServletRequest servletRequest, ServletResponse servletResponse){
HttpServletRequest request = (HttpServletRequest) servletRequest;
String token = request.getHeader("Authorization");
if (Strings.isNullOrEmpty(token)){
return new JwtToken("");
}
return new JwtToken(token);
}
}
3. 自定义Token
自定义AuthenticationToken,用于Realm验证
package com.yang.server.config.shiro;
import org.apache.shiro.authc.AuthenticationToken;
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;
}
}
4. JwtUtil
- 生成token
- 验证token是否过期
- 刷新token
package com.yang.server.config.shiro;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.shiro.authc.AuthenticationToken;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtTokenUtil {
private final String CLAIM_KEY_USERNAME = "sub";
private final String CLAIM_KEY_CREATED = "created";
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
/**
* 根据用户信息生成token字符串
* @param username
* @return
*/
public String generateToken(String username){
HashMap<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, username);
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
/**
* 根绝负载生成token
* @param claims
* @return
*/
private String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationTime())
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
/**
* 生成过期时间
* @return
*/
private Date generateExpirationTime() {
return new Date(System.currentTimeMillis() + expiration);
}
/**
* 从token中获取用户名
* @param token
* @return
*/
public String getUserNameFromToken(String token){
String username;
try{
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e){
e.printStackTrace();
return null;
}
return username;
}
/**
* 从token中获取荷载
* @param token
* @return
*/
public Claims getClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
/**
* 判断token是否过期
* @param token
* @return
*/
public boolean isTokenExpired(String token) {
Date expiredDate = getExpirationDateFromToken(token);
return expiredDate.before(new Date());
}
/**
* 获取token过期时间
* @param token
* @return
*/
private Date getExpirationDateFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.getExpiration();
}
/**
* 判断token是否可以刷新
* @param token
* @return
*/
public boolean canRefresh(String token){
return isTokenExpired(token);
}
public String refresh(String token){
Claims claims = getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
}
5. 自定义Realm
进行验证和授权操作
package com.yang.server.config.shiro;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yang.server.mapper.AdminMapper;
import com.yang.server.pojo.Admin;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
public class AccountRealm extends AuthorizingRealm {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private AdminMapper adminMapper;
/**
* 支持jwt token
*
* @param token
* @return
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
/**
* 登录认证
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
JwtToken jwtToken = (JwtToken) authenticationToken;
String token = (String) jwtToken.getPrincipal();
// 没有token,未登录,防止登录
if (token.equals("")){
throw new UnauthenticatedException("未登录");
}
// 判断token是否过期
if (jwtTokenUtil.isTokenExpired(token)) {
throw new ExpiredCredentialsException("token过期");
}
// 未过期,判断是否存在该用户
String username = jwtTokenUtil.getUserNameFromToken(token);
System.out.println("AccountRealm===>username:" + username);
Admin admin = adminMapper.selectOne(new QueryWrapper<Admin>().eq("username", username));
// 用户不存在
if (admin == null) {
throw new UnknownAccountException("用户不存在");
}
// 用户存在, 判断用户是否锁定
System.out.println(admin.getEnabled());
if (!admin.getEnabled()) {
throw new LockedAccountException("用户被锁定");
}
// 用户未被锁定,放行
return new SimpleAuthenticationInfo(jwtToken.getPrincipal(), jwtToken.getCredentials(), getName());
}
/**
* 授权
*
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
}