登錄密碼驗(yàn)證流程
1.入口
- 獲取前端傳過來的用戶信息User
- 把用戶信息User傳入到實(shí)例對(duì)象UsernamePasswordToken
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","666");
//UsernamePasswordToken 的屬性
// getUsername 賬戶,賬號(hào)
// getPassword 密碼
// getPrincipal 身份
// getCredentials 加密憑據(jù)
// getHost 主機(jī)地址
// isRememberMe 是否記住賬號(hào)密碼
- 調(diào)用subject的login方法開始進(jìn)行登錄驗(yàn)證
Subject subject = SecurityUtils.getSubject();
//此處傳的token是實(shí)例化后的對(duì)象UsernamePasswordToken
subject.login(token);
2.密碼匹配校驗(yàn)
- 創(chuàng)建自定義的relam類繼承 AuthorizingRealm 實(shí)現(xiàn)doGetAuthenticationInfo 方法和doGetAuthorizationInfo方法
- 進(jìn)入到 自定義的relam里的doGetAuthenticationInfo方法里邊進(jìn)行獲取傳過來的用戶信息,然后進(jìn)行密碼匹配,
-
隨后按照密碼的加密方式選擇密碼匹配器,自定義的話 選擇對(duì)應(yīng)的繼承就好了
密碼匹配規(guī)則類
doGetAuthorizationInfo
這個(gè)方法主要用來校驗(yàn)用戶的權(quán)限,那什么時(shí)候會(huì)進(jìn)入這個(gè)方法?
- subject.hasRole(“admin”) 或 subject.isPermitted(“admin”):自己去調(diào)用這個(gè)是否有什么角色或者是否有什么權(quán)限的時(shí)候;
- @RequiresRoles("admin") :在方法上加注解的時(shí)候;
- [@shiro.hasPermission name = "admin"][/@shiro.hasPermission]:在頁(yè)面上加shiro標(biāo)簽的時(shí)候,即進(jìn)這個(gè)頁(yè)面的時(shí)候掃描到有這個(gè)標(biāo)簽的時(shí)候
------之后生成session
登錄狀態(tài)驗(yàn)證流程
1. 入口
- 所有的登錄以及未登錄的請(qǐng)求都先進(jìn)入到filter中,按照filter的層級(jí)來進(jìn)行層層校驗(yàn)URL地址
- isAccessAllowed 所有的接口都會(huì)先進(jìn)入此方法,如果此接口返回true,就判定為已登陸,如果返回false就判定為未登錄 然后進(jìn)入到onAccessDenied方法中 isAccessAllowed 方法的mappedValue 是登錄成功的用戶的權(quán)限信息,可以用來判斷此用戶擁有那些權(quán)限
- onAccessDenied 判定此用戶未登錄是否可以訪問此頁(yè)面或者接口
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 創(chuàng)建Token
*/
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
System.out.println("進(jìn)到了創(chuàng)建TOKEN的方法中");
return null;
}
/**
* @Summary
* @Param: [request, response, mappedValue]
* @Return: boolean
* @Author: TheRaging
* @Date: 2020/10/26 23:43
* @Description 判斷是否登錄,主要用于權(quán)限校驗(yàn), false的話就代表未登錄,直接進(jìn)入onAccessDenied方法,
* 如果為true就代表已經(jīng)登錄過,然后直接訪問控制器
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
/*
//權(quán)限訪問邏輯
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue;
//沒有權(quán)限訪問
if (rolesArray == null || rolesArray.length == 0) {
return true;
}
for (int i = 0; i < rolesArray.length; i++) {
//若當(dāng)前用戶是rolesArray中的任何一個(gè),則有權(quán)限訪問
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的話 選喲解析驗(yàn)證token 驗(yàn)證通過的話就直接權(quán)限校驗(yàn)或者直接訪問,如果校驗(yàn)不通過的話就拒絕登錄
System.out.println("獲取到了TOKEN-----------"+token);
return true;
}
}
/**
* @Summary
* @Param: [request, response]
* @Return: boolean
* @Author: TheRaging
* @Date: 2020/10/26 23:44
* @Description 未登錄狀態(tài)進(jìn)入此方法,
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
System.out.println("進(jìn)入到了onAccessDenied方法中");
return false;
}
/**
* @Summary
* @Param: [token, e, request, response]
* @Return: boolean
* @Author: TheRaging
* @Date: 2020/10/26 23:46
* @Description TODO 登錄失敗的一個(gè)處理
*/
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
System.out.println("進(jìn)入到了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 登錄成功的一個(gè)處理
*/
@Override
protected boolean onLoginSuccess(AuthenticationToken token,
Subject subject, ServletRequest request, ServletResponse response)
throws Exception {
System.out.println("進(jìn)入到了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;
}
/**
* 獲取請(qǐng)求的token
*/
private String getRequestToken(HttpServletRequest httpRequest){
//從header中獲取token
String token = httpRequest.getHeader("token");
//如果header中不存在token,則從參數(shù)中獲取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用于登錄驗(yàn)證邏輯,返回自定義的ShiroRealm 相當(dāng)于一個(gè)數(shù)據(jù)源
* 身份驗(yàn)證(getAuthenticationInfo 方法)驗(yàn)證賬戶和密碼,并返回相關(guān)信息
* 權(quán)限獲?。╣etAuthorizationInfo 方法) 獲取指定身份的權(quán)限,并返回相關(guān)信息
* 令牌支持(supports方法)判斷該令牌(Token)是否被支持
*/
@Bean
public MyRealm myShiroRealm() {
MyRealm customRealm = new MyRealm();
//自定義密碼驗(yàn)證
customRealm.setCredentialsMatcher(myCredentialsMatcher());
return customRealm;
}
/**
* @Summary
* @Param: []
* @Return: java.lang.SecurityManager
* @Author: TheRaging
* @Date: 2020/10/23 22:29
* @Description 配置SecurityManager,用于管理認(rèn)證
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
//設(shè)置自定義的session管理器
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* @Summary
* @Param: [securityManager]
* @Return: ShiroFilterFactoryBean
* @Author: TheRaging
* @Date: 2020/10/23 22:30
* @Description 配置ShiroFilterFactoryBean過濾器,用于過濾url地址,然后決定哪些路徑需要認(rèn)證
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//設(shè)置使用自定義的filter進(jìn)行過濾
Map<String, Filter> filters = new HashMap<>();
filters.put("filter", new MyFilter());
//配置自定義的過濾規(guī)則
shiroFilterFactoryBean.setFilters(filters);
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> map = new HashMap<>();
//登錄
map.put("/test/login", "anon");
//登出
map.put("/logout", "logout");
//登錄失敗跳轉(zhuǎn)的頁(yè)面
// shiroFilterFactoryBean.setLoginUrl("/test/login");
//配置登錄成功的跳轉(zhuǎn)頁(yè)面,默認(rèn)跳轉(zhuǎn)到/
//shiroFilterFactoryBean.setSuccessUrl("/index");
//沒有權(quán)限,權(quán)限校驗(yàn)失敗跳轉(zhuǎn)的頁(yè)面
//shiroFilterFactoryBean.setUnauthorizedUrl("/error");
//對(duì)所有用戶認(rèn)證
map.put("/**", "filter");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
/**
* shiro緩存管理器
* 1 添加相關(guān)的maven支持
* 2 注冊(cè)這個(gè)bean,將緩存的配置文件導(dǎo)入
* 3 在securityManager 中注冊(cè)緩存管理器,之后就不會(huì)每次都會(huì)去查詢數(shù)據(jù)庫(kù)了,相關(guān)的權(quán)限和角色會(huì)保存在緩存中,但需要注意一點(diǎn),更新了權(quán)限等操作之后,需要及時(shí)的清理緩存
*/
/* @Bean
public EhCacheManager ehCacheManager() {
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:config/ehcache.xml");
return cacheManager;
}*/
/**
* 自定義的 shiro session 緩存管理器,用于跨域等情況下使用 token 進(jìn)行驗(yàn)證,不依賴于sessionId
* @return
*/
@Bean
public SessionManager sessionManager(){
//將我們繼承后重寫的shiro session 注冊(cè)
MySession shiroSession = new MySession();
//如果后續(xù)考慮多tomcat部署應(yīng)用,可以使用shiro-redis開源插件來做session 的控制,或者nginx 的負(fù)載均衡
shiroSession.setSessionDAO(new EnterpriseCacheSessionDAO());
return shiroSession;
}
/**
* @Summary
* @Param: []
* @Return: MyCredentialsMatcher
* @Author: TheRaging
* @Date: 2020/10/23 23:45
* @Description 配置自定義的密碼校驗(yàn)類MyCredentialsMatcher
* MyCredentialsMatcher 需要繼承CredentialsMatcher 實(shí)現(xiàn)重寫
* 但是同時(shí)需要在SecurityManager中設(shè)置
* 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 配置實(shí)現(xiàn)自定義的securityManager
* MySessionManager需要繼承 DefaultWebSessionManager 實(shí)現(xiàn)重寫
* 但是同時(shí)也需要在 securityManager 中添加
* SecurityManager.setSessionManager(sessionManager());
*/
/* @Bean
public SessionManager sessionManager(){
MySessionManager mySessionManager = new MySessionManager();
//這里可以不設(shè)置。Shiro有默認(rèn)的session管理。如果緩存為Redis則需改用Redis的管理
mySessionManager.setSessionDAO(new EnterpriseCacheSessionDAO());
return mySessionManager;
}*/
//DefaultAdvisorAutoProxyCreator 和AuthorizationAttributeSourceAdvisor兩個(gè)在一起才能支Shiro的注解權(quán)限控制實(shí)現(xiàn)
/**
* @Summary
* @Param: []
* @Return: org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator
* @Author: TheRaging
* @Date: 2020/10/23 22:33
* @Description 配置,這個(gè)是AOP的,相當(dāng)于一個(gè)切面
* 他會(huì)掃描所有的類中的Advisor,
* 然后這些Advisor應(yīng)用到所有符合切入點(diǎn)的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 這個(gè)類就相當(dāng)于切點(diǎn)了
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
