shiro 集成 jwt 需要禁用 session, 服务器就不用维护用户的状态, 做到无状态调用
可以参考我的GitHub测试项目 https://github.com/zdtdtel/springboot-shiro-jwt-demo
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
1. 定义一个 JwtToken, 用来封装 username 和 jsonWebToken
package com.codingos.shirojwt.shiro;
import org.apache.shiro.authc.AuthenticationToken;
public class JwtToken implements AuthenticationToken{
private static final long serialVersionUID = 5467074955086481181L;
private String username;
private String jsonWebToken;
public JwtToken(String username, String jsonWebToken) {
this.username = username;
this.jsonWebToken = jsonWebToken;
}
@Override
public Object getPrincipal() {
return username;
}
@Override
public Object getCredentials() {
return jsonWebToken;
}
}
2. 自定义一个 Filter 用来过滤所有请求, 交给realm进行验证
package com.codingos.shirojwt.shiro;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.codingos.shirojwt.common.CommonUtils;
public class CustomFilter extends AccessControlFilter{
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
if(logger.isDebugEnabled()) {
logger.debug("访问的URI: {}", ((HttpServletRequest) request).getRequestURI());
}
String jsonWebToken = CommonUtils.getJsonWebToken((HttpServletRequest) request);
String username = "";
if (StringUtils.isBlank(jsonWebToken)) {
jsonWebToken = "";
} else {
// 解码 jwt
DecodedJWT decodeJwt = JWT.decode(jsonWebToken);
username = decodeJwt.getClaim("username").asString();
}
JwtToken token = new JwtToken(username, jsonWebToken);
try {
// 交给自定义realm进行jwt验证和对应角色,权限的查询
getSubject(request, response).login(token);
} catch (AuthenticationException e) {
request.setAttribute("msg", "认证失败");
// 转发给指定的 controller, 进行统一异常处理
((HttpServletRequest)request).getRequestDispatcher("/exception").forward(request, response);
return false;
}
return true;
}
}
3. 创建自定义realm, 用来验证 jwt , role, permission
package com.codingos.shirojwt.shiro;
import java.util.Set;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.codingos.shirojwt.constant.Constant;
import com.codingos.shirojwt.service.ShiroService;
public class CustomRealm extends AuthorizingRealm {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ShiroService shiroService;
public CustomRealm() {
}
@Override
public boolean supports(AuthenticationToken token) {
// 仅支持 JwtToken
return token instanceof JwtToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取用户名, 用户唯一标识
String username = (String) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
Set<String> permissionSet = shiroService.listPermissions(username);
permissionSet.add("perm-1"); // 造数据, 假装是从数据库查出来的
permissionSet.add("perm-2");
simpleAuthorizationInfo.setStringPermissions(permissionSet);
Set<String> roleSet = shiroService.listRoles(username);
roleSet.add("role-1"); // 造数据, 假装是从数据库查出来的
roleSet.add("role-2");
simpleAuthorizationInfo.setRoles(roleSet);
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
String jsonWebToken = (String) token.getCredentials();
Algorithm algorithm = Algorithm.HMAC256(Constant.JWT_SECRET);
JWTVerifier verifier = JWT.require(algorithm).build();
try {
verifier.verify(jsonWebToken);
if(logger.isDebugEnabled()) {
logger.debug("********************* 验证通过 ***********************");
}
} catch (JWTVerificationException e) {
if(logger.isDebugEnabled()) {
logger.debug("********************* 验证不通过 **********************");
}
jsonWebToken = "invalid jwt";
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, jsonWebToken, getName());
return simpleAuthenticationInfo;
}
}
4. 定义一个 WebSubjectFactory, 用来禁用 session
package com.codingos.shirojwt.shiro;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
public class CustomWebSubjectFactory extends DefaultWebSubjectFactory {
@Override
public Subject createSubject(SubjectContext context) {
// 禁用session
context.setSessionCreationEnabled(false);
return super.createSubject(context);
}
}
5. 定义一个 shiro 的配置类
package com.codingos.shirojwt.shiro;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
@SpringBootConfiguration
public class ShiroConfig {
@Bean
public Realm realm() {
return new CustomRealm();
}
@Bean
public DefaultWebSubjectFactory subjectFactory() {
return new CustomWebSubjectFactory();
}
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm());
securityManager.setSubjectFactory(subjectFactory());
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator();
// 禁用 session 存储
sessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
// 禁用 rememberMe
securityManager.setRememberMeManager(null);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
Map<String, Filter> filterMap = new HashMap<>();
filterMap.put("customFilter", new CustomFilter());
shiroFilterFactoryBean.setFilters(filterMap);
Map<String, String> filterChainDefinitionMap = new HashMap<>();
filterChainDefinitionMap.put("/tologin", "anon");
filterChainDefinitionMap.put("/exception", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/error", "anon");
filterChainDefinitionMap.put("/todenied", "anon");
filterChainDefinitionMap.put("/**", "customFilter");
// filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
shiroFilterFactoryBean.setLoginUrl("/tologin");
shiroFilterFactoryBean.setSuccessUrl("/indexpage");
shiroFilterFactoryBean.setUnauthorizedUrl("/todenied");
return shiroFilterFactoryBean;
}
}
在shiro禁用session后, 如果再使用 shiro 的内置过滤器 authc, 就会报错, 所以就不用再用 authc, 使用自定义的那个Filter就行
org.apache.shiro.subject.support.DisabledSessionException: Session creation has been disabled for the current subject. This exception indicates that there is either a programming error (using a session when it should never be used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created for the current Subject. See the org.apache.shiro.subject.support.DisabledSessionException JavaDoc for more.
来源:oschina
链接:https://my.oschina.net/zdtdtel/blog/4494733