Shiro认证流程源码分析
subject.login() ——> DelegatingSubject.login() ——> DefaultSecurityManager.login() ——> AbstractAuthenticator的authenticate() ——> ModularRealmAuthentiscat的doAuthenticate() ——> AuthenticatingRealm的抽象方法doGetAuthenticationInfo() ——> 自定义Realm重写doGetAuthenticationInfo(需继承AuthorizingRealm,AuthorizingRealm继承自AuthenticatingRealm)进行认证
DelegatingSubject 源码如下:
public class DelegatingSubject implements Subject {
......
public void login(AuthenticationToken token) throws AuthenticationException {
this.clearRunAsIdentitiesInternal();
// 此处继续调用login() -> DefaultSecurityManager.login()
Subject subject = this.securityManager.login(this, token);
// ...略
}
DefaultSecurityManager 的 login() 源码如下:
public class DefaultSecurityManager extends SessionsSecurityManager {
......
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
//此处会继续调用AbstractAuthenticator的authenticate()方法
info = this.authenticate(token);
} catch (AuthenticationException var7) {
AuthenticationException ae = var7;
try {
this.onFailedLogin(token, ae, subject);
} catch (Exception var6) {
if (log.isInfoEnabled()) {
log.info("onFailedLogin method threw an exception. Logging and propagating original AuthenticationException.", var6);
}
}
throw var7;
}
Subject loggedIn = this.createSubject(token, info, subject);
this.onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
}
AbstractAuthenticator 的 authenticate
源码如下:
public abstract class AbstractAuthenticator implements Authenticator, LogoutAware {
......
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
if (token == null) {
throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
} else {
log.trace("Authentication attempt received for token [{}]", token);
AuthenticationInfo info;
try {
// 调用自己的抽象方法
info = this.doAuthenticate(token);
// ......略
return info;
}
}
protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken var1) throws AuthenticationException;
}
ModularRealmAuthentiscator 实现了 doAuthenticate
,源码如下:
public class ModularRealmAuthenticator extends AbstractAuthenticator {
......
//1.得到自己注册的Realm类
//2.判断Realm的个数,然后执行doSingleRealmAuthentication或者doMultiRealmAuthentication
//3.在定义了多个Realm的情况下,可以自定义一个类继承ModularRealmAuthenticator,重写doAuthenticate,然后根据不同的类型调用不同的Realm
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
this.assertRealmsConfigured();
Collection<Realm> realms = this.getRealms();
return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
}
}
Shiro整合jwt实现权限认证和授权
自定义JwtUtils
工具类
主要用于对token
的生成和校验
@Slf4j
public class JwtUtils {
private static final String sign = "test$";
/**
* 描述:生成token
*
* @author zzy
* @date 2022/10/26 15:06
* @param claims 载荷,使用Sting-Object形式传入Map集合
*/
public static String getToken(Map<String,Object> claims) {
log.info("getToken claims:{}",claims);
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE ,1);
String token = JWT.create()
.withClaim("user", claims)
.withExpiresAt(calendar.getTime())
.sign(Algorithm.HMAC256(sign));
log.info("token success : {}",token);
return token;
}
/**
* 描述:校验token,并拿到载荷
*
* @author zzy
* @date 2022/10/26 15:07
* @param token 传入需要校验的token
*/
public static Claim getClaim(String token) {
DecodedJWT decodedJWT = JWT.decode(token);
Claim claim;
try {
claim = decodedJWT.getClaim("user");
return claim;
}catch (JWTDecodeException e) {
e.printStackTrace();
log.debug("claims is null");
}
return null;
}
/**
* 描述:校验token是否过期
*
* @author zzy
* @date 2022/10/26 15:17
* @param token 传入要校验的token
*/
public static boolean verifyToken(String token) {
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(sign)).build();
try {
jwtVerifier.verify(token);
} catch (TokenExpiredException e) {
e.printStackTrace();
log.info("token:{} 已过期",token);
throw e;
} catch (JWTVerificationException e) {
log.error("token:{} 无效",token);
throw e;
}
return true;
}
}
自定义 JwtToken
JwtToken继承 AuthenticationToken ,用于subject.login()的入参
public class JwtToken implements AuthenticationToken {
private String token;
public JwtToken() {
}
public JwtToken(String token) {
this.token = token;
}
@Override
public Object getCredentials() {
return token;
}
@Override
public Object getPrincipal() {
return token;
}
}
自定义JwtTokenFilter
继承 AccessControlFilter ,用于拦截所用请求,并通过subjet.login()
调用自定义 realm 中的认证方法来校验 token
public class JwtTokenFilter extends AccessControlFilter {
/**
* 若此方法返回true,则不会执行onAccessDenied()
*
*/
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
HttpServletRequest req = (HttpServletRequest) servletRequest;
// 解决跨域问题,放行options请求
return HttpMethod.OPTIONS.toString().matches(req.getMethod());
}
/**
* 描述:拦截所有请求,用shiro进行token认证
*
* @author zzy
* @date 2022/10/26 16:37
*/
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
HttpServletRequest request = (HttpServletRequest) servletRequest;
if (haveTokenHeader(request,servletResponse)) {
String token = request.getHeader("Authorization");
try {
//调用自定义realm,传入自定义JwtToken,校验token
getSubject(request, servletResponse).login(new JwtToken(token));
return true;
}catch (Exception ignored){
}
}
onLoginFail(servletResponse);
return false;
}
/**
* 描述:判断请求头中是否带有token字段
*
* @author zzy
* @date 2022/10/26 16:22
*/
private boolean haveTokenHeader(HttpServletRequest request, ServletResponse servletResponse) {
String authentication = request.getHeader("Authorization");
return authentication != null;
}
/**
* 描述:登录失败
*
* @author zzy
* @date 2022/10/26 16:51
*/
private void onLoginFail(ServletResponse response) throws IOException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.setContentType("application/json;charset=utf-8");
Map<String,Object> result = new HashMap<>();
result.put("code", HttpServletResponse.SC_UNAUTHORIZED);
result.put("msg", "login fail");
ObjectMapper objectMapper = new ObjectMapper();
httpResponse.getWriter().write(objectMapper.writeValueAsString(result));
}
}
自定义 TokenRealm
实现token的校验以及对该用户授权
public class TokenRealm extends AuthorizingRealm {
/**
* 描述:防止自定义的JwtToken不属于AuthenticationToken
*
* @author zzy
* @date 2022/10/26 17:15
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
/**
* 描述:授权
*
* @author zzy
* @date 2022/10/26 15:37
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
Set<String> roles = new HashSet<>();
//从数据中查询然后添加角色和权限
//roles.add("admin");
simpleAuthorizationInfo.setRoles(roles);
return simpleAuthorizationInfo;
}
/**
* 描述:认证
*
* @author zzy
* @date 2022/10/26 15:37
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String token = (String) authenticationToken.getPrincipal();
boolean verifyToken;
try {
verifyToken = JwtUtils.verifyToken(token);
} catch (TokenExpiredException e) {
throw new ExpiredCredentialsException();
} catch (JWTVerificationException e) {
throw new IncorrectCredentialsException();
}
return verifyToken ? new SimpleAuthenticationInfo(token, token, this.getName()) : null;
}
}
自定义**subject
工厂**
禁用session
,达到无状态
/**
* 描述:自定义subject工厂,禁用session
*
* @author zzy
* @date 2022/10/26 19:22
*/
public class BanSessionSubjectFactory extends DefaultWebSubjectFactory {
@Override
public Subject createSubject(SubjectContext context) {
context.setSessionCreationEnabled(false);
return super.createSubject(context);
}
}
配置ShiroConfig
需要实现以下几点:
- 注册全局安全管理器DefaultWebSecurityManager
- 在DefaultWebSecurityManager注册自定义的Realm
- 在DefaultWebSecurityManager注册自定义的subject工厂,禁用session,禁用remember me
- 注册ShiroFilterFactoryBean,使用自定的Filter拦截路径校验token
@Configuration
public class ShiroConfig {
/**
* 描述:注册tokenRealm
*
* @author zzy
* @date 2022/10/26 16:53
*/
@Bean("realms")
public Realm realms() {
return new TokenRealm();
}
/**
* 描述:注册subject工厂
*
* @author zzy
* @date 2022/10/26 19:23
*/
@Bean("subjectFactory")
public DefaultWebSubjectFactory subjectFactory() {
return new BanSessionSubjectFactory();
}
/**
* 描述:注册全局安全管理器
*
* @author zzy
* @date 2022/10/26 16:53
*/
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(Realm realm,DefaultWebSubjectFactory subjectFactory) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
//注册subject工厂
defaultWebSecurityManager.setSubjectFactory(subjectFactory);
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
//禁用session存储
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
defaultWebSecurityManager.setSubjectDAO(subjectDAO);
//禁用remember me
defaultWebSecurityManager.setRememberMeManager(null);
return defaultWebSecurityManager;
}
/**
* 描述:注册shiro过滤器
*
* @author zzy
* @date 2022/10/26 16:55
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//注册过滤器
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("jwt", new JwtTokenFilter());
Map<String, String> filters = new HashMap<>();
filters.put("/login", "anon");
//不再使用shiro内置的authc过滤器
filters.put("/**", "jwt");
shiroFilterFactoryBean.setFilters(filterMap);
shiroFilterFactoryBean.setFilterChainDefinitionMap(filters);
return shiroFilterFactoryBean;
}
}
配置统一异常配置类捕获认证异常、权限异常
@ControllerAdvice
@RestController
public class ExceptionHandler {
/**
* 描述:捕获权限不足异常
*
* @author zzy
* @date 2022/10/26 22:15
*/
@org.springframework.web.bind.annotation.ExceptionHandler(value = UnauthorizedException.class)
public Map<String,Object> UnauthorizedException() {
Map<String,Object> result = new HashMap<>();
result.put("code", 405);
result.put("msg", "权限不足!");
return result;
}
}