登录密码验证流程
1.入口
- 获取前端传过来的用户信息User
- 把用户信息User传入到实例对象UsernamePasswordToken
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","666");
//UsernamePasswordToken 的属性
// getUsername 账户,账号
// getPassword 密码
// getPrincipal 身份
// getCredentials 加密凭据
// getHost 主机地址
// isRememberMe 是否记住账号密码
- 调用subject的login方法开始进行登录验证
Subject subject = SecurityUtils.getSubject();
//此处传的token是实例化后的对象UsernamePasswordToken
subject.login(token);
2.密码匹配校验
- 创建自定义的relam类继承 AuthorizingRealm 实现doGetAuthenticationInfo 方法和doGetAuthorizationInfo方法
- 进入到 自定义的relam里的doGetAuthenticationInfo方法里边进行获取传过来的用户信息,然后进行密码匹配,
-
随后按照密码的加密方式选择密码匹配器,自定义的话 选择对应的继承就好了
doGetAuthorizationInfo
这个方法主要用来校验用户的权限,那什么时候会进入这个方法?
- subject.hasRole(“admin”) 或 subject.isPermitted(“admin”):自己去调用这个是否有什么角色或者是否有什么权限的时候;
- @RequiresRoles("admin") :在方法上加注解的时候;
- [@shiro.hasPermission name = "admin"][/@shiro.hasPermission]:在页面上加shiro标签的时候,即进这个页面的时候扫描到有这个标签的时候
------之后生成session
登录状态验证流程
1. 入口
- 所有的登录以及未登录的请求都先进入到filter中,按照filter的层级来进行层层校验URL地址
- isAccessAllowed 所有的接口都会先进入此方法,如果此接口返回true,就判定为已登陆,如果返回false就判定为未登录 然后进入到onAccessDenied方法中 isAccessAllowed 方法的mappedValue 是登录成功的用户的权限信息,可以用来判断此用户拥有那些权限
- onAccessDenied 判定此用户未登录是否可以访问此页面或者接口
package com.eat.config;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
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;
/**
* @version V1.0
* @Package com.eat.config
* @ClassName MyFilter
* @Description 自定义shiro的过滤器
* @Author 王振鹏
* @date 2020/10/26 23:39
**/
public class MyFilter extends AuthenticatingFilter {
/**
* @Summar
* @Param: [request, response]
* @Return: org.apache.shiro.authc.AuthenticationToken
* @Author: TheRaging
* @Date: 2020/10/26 23:46
* @Description TODO 创建Token
*/
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
System.out.println("进到了创建TOKEN的方法中");
return null;
}
/**
* @Summary
* @Param: [request, response, mappedValue]
* @Return: boolean
* @Author: TheRaging
* @Date: 2020/10/26 23:43
* @Description 判断是否登录,主要用于权限校验, false的话就代表未登录,直接进入onAccessDenied方法,
* 如果为true就代表已经登录过,然后直接访问控制器
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
/*
//权限访问逻辑
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue;
//没有权限访问
if (rolesArray == null || rolesArray.length == 0) {
return true;
}
for (int i = 0; i < rolesArray.length; i++) {
//若当前用户是rolesArray中的任何一个,则有权限访问
if (subject.hasRole(rolesArray[i])) {
return true;
}
}*/
ShiroHttpServletRequest rrr = (ShiroHttpServletRequest)request;
String s = rrr.getRequestURI();
System.out.println(s);
String token = getRequestToken((HttpServletRequest)request);
if(StringUtils.isEmpty(token)){
//没有token的话 就去登录
System.out.println("tojkren-----------"+token);
return false;
}else {
//有token的话 选哟解析验证token 验证通过的话就直接权限校验或者直接访问,如果校验不通过的话就拒绝登录
System.out.println("获取到了TOKEN-----------"+token);
return true;
}
}
/**
* @Summary
* @Param: [request, response]
* @Return: boolean
* @Author: TheRaging
* @Date: 2020/10/26 23:44
* @Description 未登录状态进入此方法,
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
System.out.println("进入到了onAccessDenied方法中");
return false;
}
/**
* @Summary
* @Param: [token, e, request, response]
* @Return: boolean
* @Author: TheRaging
* @Date: 2020/10/26 23:46
* @Description TODO 登录失败的一个处理
*/
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
System.out.println("进入到了onLoginFailure方法中");
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json;charset=utf-8");
try {
//处理登录失败的异常
Throwable throwable = e.getCause() == null ? e : e.getCause();
/* R r = R.error(HttpStatus.SC_UNAUTHORIZED, throwable.getMessage());
String json = new Gson().toJson(r);
httpResponse.getWriter().print(json);*/
httpResponse.getWriter().print("2123");
} catch (IOException e1) {
}
return false;
}
/**
* @Summary
* @Param: [token, subject, request, response]
* @Return: boolean
* @Author: TheRaging
* @Date: 2020/10/26 23:47
* @Description TODO 登录成功的一个处理
*/
@Override
protected boolean onLoginSuccess(AuthenticationToken token,
Subject subject, ServletRequest request, ServletResponse response)
throws Exception {
System.out.println("进入到了onLoginSuccess方法中");
Session session = SecurityUtils.getSubject().getSession();
/*UserInfo user = (UserInfo) subject.getPrincipal();
session.setAttribute("userName", user.getUserName());
session.setAttribute("userId", user.getUserId());*/
issueSuccessRedirect(request, response);
return false;
}
/**
* 获取请求的token
*/
private String getRequestToken(HttpServletRequest httpRequest){
//从header中获取token
String token = httpRequest.getHeader("token");
//如果header中不存在token,则从参数中获取token
if(StringUtils.isBlank(token)){
token = httpRequest.getParameter("token");
}
return token;
}
}
shiro的配置文件
package com.eat.config;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;
/**
* @version V1.0
* @Package com.eat.config
* @ClassName ShiroConfig
* @Description TODO
* @Author 王振鹏
* @date 2020/10/23 22:22
**/
@Configuration
public class ShiroConfig {
/**
* @Summary
* @Param: []
* @Return: CustomRealm
* @Author: TheRaging
* @Date: 2020/10/23 22:28
* @Description 配置ShiroRealm用于登录验证逻辑,返回自定义的ShiroRealm 相当于一个数据源
* 身份验证(getAuthenticationInfo 方法)验证账户和密码,并返回相关信息
* 权限获取(getAuthorizationInfo 方法) 获取指定身份的权限,并返回相关信息
* 令牌支持(supports方法)判断该令牌(Token)是否被支持
*/
@Bean
public MyRealm myShiroRealm() {
MyRealm customRealm = new MyRealm();
//自定义密码验证
customRealm.setCredentialsMatcher(myCredentialsMatcher());
return customRealm;
}
/**
* @Summary
* @Param: []
* @Return: java.lang.SecurityManager
* @Author: TheRaging
* @Date: 2020/10/23 22:29
* @Description 配置SecurityManager,用于管理认证
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
//设置自定义的session管理器
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* @Summary
* @Param: [securityManager]
* @Return: ShiroFilterFactoryBean
* @Author: TheRaging
* @Date: 2020/10/23 22:30
* @Description 配置ShiroFilterFactoryBean过滤器,用于过滤url地址,然后决定哪些路径需要认证
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置使用自定义的filter进行过滤
Map<String, Filter> filters = new HashMap<>();
filters.put("filter", new MyFilter());
//配置自定义的过滤规则
shiroFilterFactoryBean.setFilters(filters);
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> map = new HashMap<>();
//登录
map.put("/test/login", "anon");
//登出
map.put("/logout", "logout");
//登录失败跳转的页面
// shiroFilterFactoryBean.setLoginUrl("/test/login");
//配置登录成功的跳转页面,默认跳转到/
//shiroFilterFactoryBean.setSuccessUrl("/index");
//没有权限,权限校验失败跳转的页面
//shiroFilterFactoryBean.setUnauthorizedUrl("/error");
//对所有用户认证
map.put("/**", "filter");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
/**
* shiro缓存管理器
* 1 添加相关的maven支持
* 2 注册这个bean,将缓存的配置文件导入
* 3 在securityManager 中注册缓存管理器,之后就不会每次都会去查询数据库了,相关的权限和角色会保存在缓存中,但需要注意一点,更新了权限等操作之后,需要及时的清理缓存
*/
/* @Bean
public EhCacheManager ehCacheManager() {
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:config/ehcache.xml");
return cacheManager;
}*/
/**
* 自定义的 shiro session 缓存管理器,用于跨域等情况下使用 token 进行验证,不依赖于sessionId
* @return
*/
@Bean
public SessionManager sessionManager(){
//将我们继承后重写的shiro session 注册
MySession shiroSession = new MySession();
//如果后续考虑多tomcat部署应用,可以使用shiro-redis开源插件来做session 的控制,或者nginx 的负载均衡
shiroSession.setSessionDAO(new EnterpriseCacheSessionDAO());
return shiroSession;
}
/**
* @Summary
* @Param: []
* @Return: MyCredentialsMatcher
* @Author: TheRaging
* @Date: 2020/10/23 23:45
* @Description 配置自定义的密码校验类MyCredentialsMatcher
* MyCredentialsMatcher 需要继承CredentialsMatcher 实现重写
* 但是同时需要在SecurityManager中设置
* SecurityManager.setCredentialsMatcher(myCredentialsMatcher());
*/
@Bean
public MyCredentialsMatcher myCredentialsMatcher() {
return new MyCredentialsMatcher();
}
/**
* @Summary
* @Param: []
* @Return: org.apache.shiro.mgt.SecurityManager
* @Author: TheRaging
* @Date: 2020/10/23 23:51
* @Description 配置实现自定义的securityManager
* MySessionManager需要继承 DefaultWebSessionManager 实现重写
* 但是同时也需要在 securityManager 中添加
* SecurityManager.setSessionManager(sessionManager());
*/
/* @Bean
public SessionManager sessionManager(){
MySessionManager mySessionManager = new MySessionManager();
//这里可以不设置。Shiro有默认的session管理。如果缓存为Redis则需改用Redis的管理
mySessionManager.setSessionDAO(new EnterpriseCacheSessionDAO());
return mySessionManager;
}*/
//DefaultAdvisorAutoProxyCreator 和AuthorizationAttributeSourceAdvisor两个在一起才能支Shiro的注解权限控制实现
/**
* @Summary
* @Param: []
* @Return: org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator
* @Author: TheRaging
* @Date: 2020/10/23 22:33
* @Description 配置,这个是AOP的,相当于一个切面
* 他会扫描所有的类中的Advisor,
* 然后这些Advisor应用到所有符合切入点的Bean中
*/
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
/**
* @Summary
* @Param: [securityManager]
* @Return: AuthorizationAttributeSourceAdvisor
* @Author: TheRaging
* @Date: 2020/10/23 22:38
* @Description 配置 aop 这个类就相当于切点了
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}