最近学习了一下shiro 大体流程是走通了 想了解下源码 也好进一步了解其运行原理
首先是登录验证从页面的登陆请求开始
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String loginVali() {
String username = super.getPara("username").trim();
String password = super.getPara("password").trim();
//记住我
String remember = super.getPara("remember");
//获取登录主体
Subject currentUser = ShiroKit.getSubject();
实例化UsernamePasswordToken
UsernamePasswordToken token = new UsernamePasswordToken(username, password.toCharArray());
//如果开启了记住我功能
if ("on".equals(remember)) {
token.setRememberMe(true);
} else {
token.setRememberMe(false);
}
//执行shiro登录操作 将创建的token作为参数进行流转
currentUser.login(token);--下面就是走到源码了
//登录成功,记录登录日志
ShiroUser shiroUser = ShiroKit.getUserNotNull();
LogManager.me().executeLog(LogTaskFactory.loginLog(shiroUser.getId(), getIp()));
ShiroKit.getSession().setAttribute("sessionFlag", true);
return REDIRECT + "/";
}
从主体的获取开始
//自定义工具类
public class ShiroKit {
/**
* 获取当前 Subject
*
* @return Subject
*/
public static Subject getSubject() {
return SecurityUtils.getSubject();
}
}
//jar 中的抽象类
//是每个请求创建一个Subject, 并保存到ThreadContext的
//resources(ThreadLocal<Map<Object, Object>>)变量中,
//也就是一个http请求一个subject,并绑定到当前线程
public abstract class SecurityUtils {
public static Subject getSubject() {
//获取ThreadContext中已存在的subject
Subject subject = ThreadContext.getSubject();
if (subject == null) {
//subject不存在就创建并保存到ThreadContext中
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
接下来的源码分析可进入下面链接https://blog.csdn.net/qq_39575279/article/details/86702171
接下来就是securityManager进行验证了
@Configuration
public class ShiroConfig {
/**
* 安全管理器
*/
@Bean
public DefaultWebSecurityManager securityManager(CookieRememberMeManager rememberMeManager, CacheManager cacheShiroManager, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(this.shiroDbRealm());
securityManager.setCacheManager(cacheShiroManager);
securityManager.setRememberMeManager(rememberMeManager);
securityManager.setSessionManager(sessionManager);
return securityManager;
}
/**
* spring session管理器(多机环境)
*/
@Bean
@ConditionalOnProperty(prefix = "guns", name = "spring-session-open", havingValue = "true")
public ServletContainerSessionManager servletContainerSessionManager() {
return new ServletContainerSessionManager();
}
/**
* session管理器(单机环境)
*/
@Bean
@ConditionalOnProperty(prefix = "guns", name = "spring-session-open", havingValue = "false")
public DefaultWebSessionManager defaultWebSessionManager(CacheManager cacheShiroManager, GunsProperties gunsProperties) {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setCacheManager(cacheShiroManager);
sessionManager.setSessionValidationInterval(gunsProperties.getSessionValidationInterval() * 1000);
sessionManager.setGlobalSessionTimeout(gunsProperties.getSessionInvalidateTime() * 1000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
Cookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
cookie.setName("shiroCookie");
cookie.setHttpOnly(true);
sessionManager.setSessionIdCookie(cookie);
return sessionManager;
}
/**
* 缓存管理器 使用Ehcache实现
*/
@Bean
public CacheManager getCacheShiroManager(EhCacheManagerFactoryBean ehcache) {
EhCacheManager ehCacheManager = new EhCacheManager();
ehCacheManager.setCacheManager(ehcache.getObject());
return ehCacheManager;
}
/**
* 项目自定义的Realm
*/
@Bean
public ShiroDbRealm shiroDbRealm() {
return new ShiroDbRealm();
}
/**
* rememberMe管理器, cipherKey生成见{@code Base64Test.java}
*/
@Bean
public CookieRememberMeManager rememberMeManager(SimpleCookie rememberMeCookie) {
CookieRememberMeManager manager = new CookieRememberMeManager();
manager.setCipherKey(Base64.decode("Z3VucwAAAAAAAAAAAAAAAA=="));
manager.setCookie(rememberMeCookie);
return manager;
}
/**
* 记住密码Cookie
*/
@Bean
public SimpleCookie rememberMeCookie() {
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
simpleCookie.setHttpOnly(true);
simpleCookie.setMaxAge(7 * 24 * 60 * 60);//7天
return simpleCookie;
}
/**
* Shiro的过滤器链
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
/**
* 默认的登陆访问url
*/
shiroFilter.setLoginUrl("/login");
/**
* 登陆成功后跳转的url
*/
shiroFilter.setSuccessUrl("/");
/**
* 没有权限跳转的url
*/
shiroFilter.setUnauthorizedUrl("/global/error");
/**
* 覆盖默认的user拦截器(默认拦截器解决不了ajax请求 session超时的问题,若有更好的办法请及时反馈作者)
*/
HashMap<String, Filter> myFilters = new HashMap<>();
myFilters.put("user", new GunsUserFilter());
shiroFilter.setFilters(myFilters);
/**
* 配置shiro拦截器链
*
* anon 不需要认证
* authc 需要认证
* user 验证通过或RememberMe登录的都可以
*
* 当应用开启了rememberMe时,用户下次访问时可以是一个user,但不会是authc,因为authc是需要重新认证的
*
* 顺序从上到下,优先级依次降低
*
* api开头的接口,走rest api鉴权,不走shiro鉴权
*
*/
Map<String, String> hashMap = new LinkedHashMap<>();
for (String nonePermissionRe : NONE_PERMISSION_RES) {
hashMap.put(nonePermissionRe, "anon");
}
hashMap.put("/**", "user");
shiroFilter.setFilterChainDefinitionMap(hashMap);
return shiroFilter;
}
/**
* 在方法中 注入 securityManager,进行代理控制
*/
@Bean
public MethodInvokingFactoryBean methodInvokingFactoryBean(DefaultWebSecurityManager securityManager) {
MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();
bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
bean.setArguments(securityManager);
return bean;
}
/**
* Shiro生命周期处理器:
* 用于在实现了Initializable接口的Shiro bean初始化时调用Initializable接口回调(例如:UserRealm)
* 在实现了Destroyable接口的Shiro bean销毁时调用 Destroyable接口回调(例如:DefaultSecurityManager)
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 启用shrio授权注解拦截方式,AOP式方法级权限检查
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
直接走到我们设置好的shiroDbRealm 类中
public class ShiroDbRealm extends AuthorizingRealm {
/**
* 登录认证
* AuthenticationToken存放的token信息即用户名与密码
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
throws AuthenticationException {
UserAuthService shiroFactory = UserAuthServiceServiceImpl.me();
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
//数据库查找的用户数据
User user = shiroFactory.user(token.getUsername());
//没有密码的user信息存放到shiroUser中做缓存
ShiroUser shiroUser = shiroFactory.shiroUser(user);
//super.getName()所在的 realm验证器
return shiroFactory.info(shiroUser, user, super.getName());
}
/**
* 权限认证
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
UserAuthService shiroFactory = UserAuthServiceServiceImpl.me();
ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
List<Long> roleList = shiroUser.getRoleList();
Set<String> permissionSet = new HashSet<>();
Set<String> roleNameSet = new HashSet<>();
for (Long roleId : roleList) {
List<String> permissions = shiroFactory.findPermissionsByRoleId(roleId);
if (permissions != null) {
for (String permission : permissions) {
if (ToolUtil.isNotEmpty(permission)) {
permissionSet.add(permission);
}
}
}
String roleName = shiroFactory.findRoleNameByRoleId(roleId);
roleNameSet.add(roleName);
}
//授权执行类
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permissionSet);
info.addRoles(roleNameSet);
return info;
}
/**
* 设置认证加密方式
*/
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
HashedCredentialsMatcher md5CredentialsMatcher = new HashedCredentialsMatcher();
md5CredentialsMatcher.setHashAlgorithmName(ShiroKit.hashAlgorithmName);
md5CredentialsMatcher.setHashIterations(ShiroKit.hashIterations);
super.setCredentialsMatcher(md5CredentialsMatcher);
}
}
在登陆时只进行第一种登录认证
有工具类方法进行验证
@Service
@DependsOn("springContextHolder")
@Transactional(readOnly = true)
public class UserAuthServiceServiceImpl implements UserAuthService {
@Autowired
private UserMapper userMapper;
@Autowired
private MenuMapper menuMapper;
@Autowired
private UserService userService;
public static UserAuthService me() {
return SpringContextHolder.getBean(UserAuthService.class);
}
@Override
public User user(String account) {
User user = userMapper.getByAccount(account);
// 账号不存在
if (null == user) {
throw new CredentialsException();
}
// 账号被冻结
if (!user.getStatus().equals(ManagerStatus.OK.getCode())) {
throw new LockedAccountException();
}
return user;
}
@Override
public ShiroUser shiroUser(User user) {
ShiroUser shiroUser = ShiroKit.createShiroUser(user);
//用户角色数组
Long[] roleArray = Convert.toLongArray(user.getRoleId());
//获取用户角色列表
List<Long> roleList = new ArrayList<>();
List<String> roleNameList = new ArrayList<>();
for (Long roleId : roleArray) {
roleList.add(roleId);
roleNameList.add(ConstantFactory.me().getSingleRoleName(roleId));
}
shiroUser.setRoleList(roleList);
shiroUser.setRoleNames(roleNameList);
return shiroUser;
}
@Override
public List<String> findPermissionsByRoleId(Long roleId) {
return menuMapper.getResUrlsByRoleId(roleId);
}
@Override
public String findRoleNameByRoleId(Long roleId) {
return ConstantFactory.me().getSingleRoleTip(roleId);
}
@Override
public SimpleAuthenticationInfo info(ShiroUser shiroUser, User user, String realmName) {
String credentials = user.getPassword();
// 密码加盐处理
String source = user.getSalt();
ByteSource credentialsSalt = new Md5Hash(source);
return new SimpleAuthenticationInfo(shiroUser, credentials, credentialsSalt, realmName);
}
}
doGetAuthenticationInfo方法通过工具类进行了最后的 shiroFactory.info(shiroUser, user, super.getName());方法中
接着 进入SimpleAuthenticationInfo类(jar包中的类)然后
public SimpleAuthenticationInfo(Object principal, Object hashedCredentials, ByteSource credentialsSalt, String realmName) {
this.principals = new SimplePrincipalCollection(principal, realmName);
this.credentials = hashedCredentials;
this.credentialsSalt = credentialsSalt;
}
方法中的SimplePrincipalCollection
public SimplePrincipalCollection(Object principal, String realmName) {
if (principal instanceof Collection) {
addAll((Collection) principal, realmName);
} else {
add(principal, realmName);
}
}
public void addAll(Collection principals, String realmName) {
if (realmName == null) {
throw new IllegalArgumentException("realmName argument cannot be null.");
}
if (principals == null) {
throw new IllegalArgumentException("principals argument cannot be null.");
}
if (principals.isEmpty()) {
throw new IllegalArgumentException("principals argument cannot be an empty collection.");
}
this.cachedToString = null;
getPrincipalsLazy(realmName).addAll(principals);
}
public void add(Object principal, String realmName) {
if (realmName == null) {
throw new IllegalArgumentException("realmName argument cannot be null.");
}
if (principal == null) {
throw new IllegalArgumentException("principal argument cannot be null.");
}
this.cachedToString = null;
getPrincipalsLazy(realmName).add(principal);
}
//返回为接口
protected Collection getPrincipalsLazy(String realmName) {
if (realmPrincipals == null) {
realmPrincipals = new LinkedHashMap<String, Set>();
}
Set principals = realmPrincipals.get(realmName);
if (principals == null) {
principals = new LinkedHashSet();
realmPrincipals.put(realmName, principals);
}
return principals;
}
至此完成认证返回一个身份信息的接口 如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。
至此认证完毕