第一次寫簡(jiǎn)書(shū),打算用來(lái)做做開(kāi)發(fā)的筆記本吧擂涛,以下是Shiro的使用案例读串,Shiro在SpringBoot框架下的使用,前端使用的是Vue腳手架,在老項(xiàng)目上測(cè)試的權(quán)限管理爹土,所以只貼上了部分代碼甥雕。
首先Apache Shiro是一個(gè)強(qiáng)大且易用的Java安全框架,執(zhí)行身份驗(yàn)證、授權(quán)胀茵、密碼和會(huì)話管理社露。使用Shiro的易于理解的API,您可以快速、輕松地獲得任何應(yīng)用程序,從最小的移動(dòng)應(yīng)用程序到最大的網(wǎng)絡(luò)和企業(yè)應(yīng)用程序琼娘。
三個(gè)核心組件:Subject, SecurityManager和 Realms.
Subject:代表了當(dāng)前用戶的安全操作峭弟,SecurityManager則管理所有用戶的安全操作。
SecurityManager:它是Shiro框架的核心脱拼,典型的Facade模式瞒瘸,Shiro通過(guò)SecurityManager來(lái)管理內(nèi)部組件實(shí)例,并通過(guò)它來(lái)提供安全管理的各種服務(wù)熄浓。
Realms: Realm充當(dāng)了Shiro與應(yīng)用安全數(shù)據(jù)間的“橋梁”或者“連接器”情臭。也就是說(shuō),當(dāng)對(duì)用戶執(zhí)行認(rèn)證(登錄)和授權(quán)(訪問(wèn)控制)驗(yàn)證時(shí)赌蔑,Shiro會(huì)從應(yīng)用配置的Realm中查找用戶及其權(quán)限信息俯在。
下面是在pom.xml中添加的依賴
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.8.20</version>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
使用redis緩存做session和權(quán)限cache的保存
一般下,Shiro的授權(quán)方式為代碼方式和注解方式娃惯,
代碼方式
Subject subject = SecurityUtils.getSubject();
// 判斷用戶身份是否為管理員
if (subject.hasRole("admin")){
//有權(quán)限do sth.
}else{
//無(wú)權(quán)限do sth.
}
// 判斷用戶是否擁有"listen:high"權(quán)限
// 注意一般權(quán)限的表達(dá)式為三段式或兩段式跷乐,以:號(hào)隔開(kāi) *:*:* 或者 *:* 這種格式
// 此處例子代碼匹配所有三段式和二段式的權(quán)限
if(subject.isPermitted("listen:high")){
//有權(quán)限do sth.
}else{
//無(wú)權(quán)限do sth.
}
注解方式
@RequestMapping(value = "/getUser")
@RequiresRoles(value = {"admin"},logical = Logical.OR)
@RequiresPermissions("listen:high")
public Object getUser() {
//do sth..
}
兩種方式各有優(yōu)劣,代碼方式可以在方法內(nèi)執(zhí)行一些復(fù)雜的邏輯業(yè)務(wù)趾浅,但是請(qǐng)求已經(jīng)進(jìn)入了程序方法中愕提,注解方式的好處是能將用戶請(qǐng)求攔截在方法外,請(qǐng)求不進(jìn)入方法皿哨,且使用較為方便浅侨,但是處理復(fù)雜的邏輯業(yè)務(wù)不是很靈活,需要自行捕捉拋出的異常類來(lái)實(shí)現(xiàn)業(yè)務(wù)邏輯。可按個(gè)人愛(ài)好選擇丐巫,該dome使用的注解方式來(lái)完成慢显。個(gè)人推薦注解方式。
以下是注解解析
@RequiresUser:當(dāng)前Subject必須是應(yīng)用的用戶,才能訪問(wèn)或調(diào)用被該注解標(biāo)注的類,實(shí)例,方法脖祈。
@RequiresRoles: 當(dāng)前Subject必須擁有所有指定的角色時(shí),才能訪問(wèn)被該注解標(biāo)注的方法刷晋。如果當(dāng)天Subject不同時(shí)擁有所有指定角色盖高,則方法不會(huì)執(zhí)行還會(huì)拋出AuthorizationException異常慎陵。
@RequiresPermissions:當(dāng)前Subject需要擁有某些特定的權(quán)限時(shí),才能執(zhí)行被該注解標(biāo)注的方法喻奥。如果當(dāng)前Subject不具有這樣的權(quán)限席纽,則方法不會(huì)被執(zhí)行。
@RequiresGuest:使用該注解標(biāo)注的類撞蚕,實(shí)例润梯,方法在訪問(wèn)或調(diào)用時(shí),當(dāng)前Subject可以是“gust”身份甥厦,不需要經(jīng)過(guò)認(rèn)證或者在原先的session中存在記錄纺铭。
@RequiresAuthentication:使用該注解標(biāo)注的類,實(shí)例刀疙,方法在訪問(wèn)或調(diào)用時(shí)舶赔,當(dāng)前Subject必須在當(dāng)前session中已經(jīng)過(guò)認(rèn)證。
該項(xiàng)目主要運(yùn)用@RequiresRoles和@RequiresPermissions兩個(gè)注解谦秧。
示例
//用戶擁有user身份則進(jìn)入
@RequiresRoles("user")
//用戶擁有必須同時(shí)屬于user和admin身份則進(jìn)入
@RequiresRoles({"user","admin"})
//用戶擁有user或者admin身份之一;修改 logical 為 OR 即可
@RequiresRoles(value={"user","admin"},logical=Logical.OR)
@RequiresPermissions 用法類似不在演示
項(xiàng)目結(jié)構(gòu)為:
Permission 權(quán)限實(shí)體
Permission.java
/**
* @author by. 不笑貓丶
* @date 2018年12月12日
*/
public class Permission {
private String name;
private String permission;
private String code;
public Permission(){}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
Roles 權(quán)身份實(shí)體
Roles.java
/**
* @author by. 不笑貓丶
* @date 2018年12月12日
*/
public class Roles {
private String role;
private String code;
private String permission;
public Roles() {}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
}
ShiroConfig Shiro配置類
ShiroConfig.java
/**
* @author by. 不笑貓丶
* @date 2018年12月12日
* shiro配置類
*/
@Configuration
public class ShiroConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Bean
public ShiroFilterFactoryBean shirFilter(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//注意過(guò)濾器配置順序不能顛倒
//配置退出過(guò)濾器竟纳,修改了默認(rèn)logou過(guò)濾器,清除相應(yīng)的緩存信息
filterChainDefinitionMap.put("/easeApi/authc/user/change/Exit", "logout");
// 配置需要攔截的鏈接
filterChainDefinitionMap.put("/easeApi/authc/**", "ShiroAuthFilter");
Map<String, Filter> filterMap = new LinkedHashMap<>();
// 自定義的登錄過(guò)濾器
filterMap.put("ShiroAuthFilter", new ShiroAuthFilter());
shiroFilterFactoryBean.setFilters(filterMap);
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 憑證匹配器
* (由于我們的密碼校驗(yàn)交給Shiro的SimpleAuthenticationInfo進(jìn)行處理了)
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:這里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(3);//散列的次數(shù)疚鲤,比如散列兩次锥累,相當(dāng)于 md5(md5(md5("")));
return hashedCredentialsMatcher;
}
/**
* 注入自定義的Realm類
**/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
// 自定義session管理 使用redis
securityManager.setSessionManager(sessionManager());
// 自定義緩存實(shí)現(xiàn) 使用redis
securityManager.setCacheManager(cacheManager());
securityManager.setRealm(myShiroRealm());
return securityManager;
}
/**
* 自定義sessionManager,使用redisSessionDAO生成并保存session
**/
@Bean(name = "sessionManager")
public SessionManager sessionManager() {
MySessionManager mySessionManager = new MySessionManager();
mySessionManager.setSessionDAO(redisSessionDAO());
return mySessionManager;
}
/**
* 配置shiro redisManager
* @return
*/
@ConfigurationProperties(prefix = "spring.redis")
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setDatabase(3);
return redisManager;
}
/**
* cacheManager 緩存 redis實(shí)現(xiàn)
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
/**
* 通過(guò)redis RedisSessionDAO shiro sessionDao層的實(shí)現(xiàn)
* 使用shiro-redis插件
*/
@Bean("redisSessionDAO")
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
/**
* 開(kāi)啟shiro aop注解支持.
* 使用代理方式;所以需要開(kāi)啟代碼支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
MyShiroRealm類自定義的Realm石咬,用于身份認(rèn)證和權(quán)限獲取。
MyShiroRealm.java
/**
* @author by. 不笑貓丶
* @date 2018年12月12日
* 自定義權(quán)限匹配
*/
public class MyShiroRealm extends AuthorizingRealm {
private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
@Autowired
private UserService userService;
@Autowired
private UserDao userDao;
//通過(guò)用戶名查找用戶擁有權(quán)限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//獲取SimpleAuthenticationInfo中傳來(lái)的username
String username = (String) principals.getPrimaryPrincipal();
Map<String,Object> map = acquire.getHashMap("username", username);
//得到用戶實(shí)體
UserPrivacy userPrivacy = (UserPrivacy) userService.GetMsgformTable(3, map);
//得到用戶身份的代碼
String role = userPrivacy.getRoleCode();
//通過(guò)身份代碼查找對(duì)應(yīng)的權(quán)限代碼的集合
List<String> perCodeList = userDao.GetRoles(role);
//權(quán)限代碼的集合查找對(duì)應(yīng)的權(quán)限表達(dá)式 如 "listen:high"
List<String> permission = userDao.GetPermission(perCodeList);
//添加用戶權(quán)限
authorizationInfo.addStringPermissions(permission);
//添加用戶角色
authorizationInfo.addRole(role);
return authorizationInfo;
}
//身份認(rèn)證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
logger.info("成功進(jìn)入ShiroRealm認(rèn)證器");
//從token中獲取用戶名.
String username = (String) token.getPrincipal();
SimpleAuthenticationInfo authenticationInfo;
Map<String,Object> map = acquire.getHashMap("username", username);
// 獲取用戶信息
UserPrivacy userPrivacy = (UserPrivacy) userService.GetMsgformTable(3, map);
if(userPrivacy != null) {
// 用戶存在卖哎,檢查用戶狀態(tài) code = 0 為保護(hù)狀態(tài)
Result result = userService.findNumcheck(username);
int CODE = result.getStatus();
if(CODE == 0){
throw new LockedAccountException();
} else {
// 用戶存在鬼悠,且不為保護(hù)狀態(tài),對(duì)密碼進(jìn)行md5轉(zhuǎn)碼并與前端傳來(lái)的password比對(duì)
String password = EncryUtils.getMD5(userPrivacy.getPassword().getBytes());
//實(shí)際項(xiàng)目中亏娜,這里可以根據(jù)實(shí)際情況做緩存焕窝,如果不做,Shiro自己也是有時(shí)間間隔機(jī)制维贺,2分鐘內(nèi)不會(huì)重復(fù)執(zhí)行該方法
authenticationInfo = new SimpleAuthenticationInfo(username,password,getName());//getName() realm name
}
} else {
return null;
}
return authenticationInfo;
}
/**
* 通過(guò)用戶名清除緩存
*/
public void clearCache(String username) {
System.out.println("調(diào)用cache清理操作");
PrincipalCollection principals = new SimplePrincipalCollection(
new UserPrivacy(username), getName());
clearCache(principals);
}
@Override
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}
AuthException類異常統(tǒng)一處理它掂,因?yàn)轫?xiàng)目是前后端分離的項(xiàng)目,異常捕捉后不能重定向溯泣,只返回json數(shù)組虐秋。此處只捕捉了UnauthorizedException異常,其他異忱伲可自行實(shí)現(xiàn)客给。
AuthException.java
/**
* @author by. 不笑貓丶
* @date 2018年12月12日
*/
@ControllerAdvice
public class AuthException {
private static final Logger logger = LoggerFactory.getLogger(AuthException.class);
@ExceptionHandler(value = UnauthorizedException.class)//處理訪問(wèn)方法時(shí)權(quán)限不足問(wèn)題
public void AuthcErrorHandler(HttpServletResponse res, Exception e) throws IOException {
logger.info("拋出UnauthorizedException權(quán)限異常");
res.setHeader("Access-Control-Allow-Credentials", "true");
res.setContentType("application/json; charset=utf-8");
res.setStatus(HttpServletResponse.SC_OK);
PrintWriter writer = res.getWriter();
Map<String, Object> map= new HashMap<>();
map.put("status", 3);
map.put("msg", "權(quán)限不足");
writer.write(JSON.toJSONString(map));
writer.close();
}
}
ShiroAuthFilter自定義的 Shiro 登錄認(rèn)證過(guò)濾器,繼承FormAuthenticationFilter類攔截權(quán)限不足的請(qǐng)求肢簿,并返回JSON數(shù)據(jù)
ShiroAuthFilter.java
/**
* @author by. 不笑貓丶
* @date 2018年12月12日
*/
public class ShiroAuthFilter extends FormAuthenticationFilter {
private static final Logger logger = LoggerFactory.getLogger(ShiroAuthFilter.class);
public ShiroAuthFilter() {
super();
}
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
//Always return true if the request's method is OPTIONS
if (request instanceof HttpServletRequest) {
if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
return true;
}
}
return super.isAccessAllowed(request, response, mappedValue);
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
logger.info("SHIROFILTER authc攔截");
HttpServletResponse res = (HttpServletResponse)response;
res.setHeader("Access-Control-Allow-Origin", "true");
res.setContentType("application/json; charset=utf-8");
res.setStatus(HttpServletResponse.SC_OK);
PrintWriter writer = res.getWriter();
Map<String, Object> map= new HashMap<>();
map.put("status", 3);
map.put("msg", "未登錄");
writer.write(JSON.toJSONString(map));
writer.close();
//return false 攔截靶剑, true 放行
return false;
}
}
ShiroAutoAuthFilter類蜻拨,自定義的WebFilter過(guò)濾器,該項(xiàng)目中用于基于cookie的自動(dòng)登錄場(chǎng)景桩引,在session過(guò)期或者關(guān)閉瀏覽器時(shí)缎讼,session失效或不存在時(shí),在用戶cookie尚存的情況下坑匠,再次請(qǐng)求被保護(hù)的資源時(shí)血崭,重新獲取認(rèn)證,在Vue單頁(yè)面的情況下無(wú)需刷新頁(yè)面重新獲取JSESSIONID笛辟,該類在JSESSIONID不存在時(shí)會(huì)自動(dòng)生成cookie分配到前端頁(yè)面中功氨。
ShiroAutoAuthFilter.java
/**
* @author by. 不笑貓丶
* @date 2018年12月12日
* /easeApi/authc/ 為受保護(hù)資源的路徑
*/
@WebFilter(filterName = "shiroAutoAuthFilter", urlPatterns = {"/easeApi/authc/*"})
public class ShiroAutoAuthFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(ShiroAutoAuthFilter.class);
@Override
public void destroy() {
}
@Autowired
private RedisUtil redisUtil;
@Autowired
private UserDao userDao;
public static final String SESSIONID = "JSESSIONID";
public static final int MAXAGE = 1800;
public static final String AUTHORIZATION = "Authorization";
@SuppressWarnings("deprecation")
@Override
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException {
logger.info("shiroAutoAuthFilter被調(diào)用");
HttpServletRequest request = (HttpServletRequest) arg0;
HttpServletResponse response = (HttpServletResponse) arg1;
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
//判斷用于自動(dòng)登錄的cookie是否存在
Cookie UIDcookie = com.music.Tools.CookieTool.getCookieByName(request, "UID");
//用戶給request添加header信息 添加Authorization頭,保證此次請(qǐng)求不被攔截手幢,實(shí)現(xiàn)不刷新頁(yè)面自動(dòng)認(rèn)證的關(guān)鍵
MyHttpServletRequestWrapper httpReq = new MyHttpServletRequestWrapper(request);
if (UIDcookie != null) {
//獲取securityManager管理器
SecurityManager securityManager = (SecurityManager)SpringUtil.getBean("securityManager");
SecurityUtils.setSecurityManager(securityManager);
String enUser = URLDecoder.decode(UIDcookie.getValue());
//得到username和password
String[] userArray = com.music.Tools.PBEUtils.decrypt(enUser).split("_");
String username = userArray[0];
String password = userArray[1];
Map<String, Object> map = acquire.getHashMap("username,password",username+","+password);
//檢查帳號(hào)密碼是否有效
boolean empty = userDao.QueryforPrivacy(map);
if (!empty) {
//攔截返回提示
authcReq(response);
return;
}
//獲取當(dāng)前JSESSIONID捷凄,判斷是否存在
Cookie SUID = CookieTool.getCookieByName(request, SESSIONID);
if(SUID == null) {
logger.info("JSESSIONID為空直接執(zhí)行登錄操作");
//JSESSIONID為空直接執(zhí)行登錄操作,并設(shè)置JSESSIONID至前端,實(shí)現(xiàn)不刷新自動(dòng)認(rèn)證
ShiroTool.authLogin(httpReq, response, username, password);
arg2.doFilter(httpReq, response);
return;
}
//判斷 JSESSIONID 是否存在redis中
boolean bol = redisUtil.hasKey(3, "shiro:session:"+SUID.getValue());
//redis檢測(cè)JSESSIONID結(jié)果為若為false則調(diào)用登錄操作
logger.info("redis檢測(cè)JSESSIONID結(jié)果為 :"+bol);
if(!bol){
//不存在執(zhí)行登錄操作
ShiroTool.authLogin(httpReq, response, username, password);
} else {
// 存在围来,判斷是否獲得認(rèn)證
logger.info("JSESSIONID存在跺涤,驗(yàn)證是否已認(rèn)證");
boolean auth = ShiroTool.isAuthenticated(SUID.getValue(), request, response);
if (!auth){
// 否,獲取認(rèn)證
logger.info("JSESSIONID未認(rèn)證监透,執(zhí)行登錄操作");
ShiroTool.authLogin(httpReq, response, username, password);
}else {
// 是桶错,打印消息
logger.info("JSESSIONID已認(rèn)證");
}
}
}
arg2.doFilter(httpReq, response);
}
private void authcReq(HttpServletResponse response) throws IOException {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("application/json; charset=utf-8");
PrintWriter writer = response.getWriter();
Map<String, Object> map= new HashMap<>();
map.put("status", 4);
map.put("msg", "未找到用戶信息");
writer.write(JSON.toJSONString(map));
writer.close();
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
ShiroTool類shiro工具類,包括判斷是否認(rèn)證的方法和獲取緩存用戶信息胀蛮,和login+設(shè)置cookie的操作
ShiroTool.java
/**
* @author by. 不笑貓丶
* @date 2018年12月12日
* Shiro 工具類
*/
public class ShiroTool {
private static final Logger logger = LoggerFactory.getLogger(ShiroTool.class);
/**
* 驗(yàn)證是否登陸
*/
public static boolean isAuthenticated(String sessionID,HttpServletRequest request,HttpServletResponse response){
boolean status = false;
SessionKey key = new WebSessionKey(sessionID,request,response);
try{
Session se = SecurityUtils.getSecurityManager().getSession(key);
Object obj = se.getAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
if(obj != null){
status = (Boolean) obj;
}
}catch(Exception e){
e.printStackTrace();
}finally{
Session se = null;
Object obj = null;
}
return status;
}
/**
* 獲取用戶登錄信息
*/
public static UserPrivacy getUserPrivacy(String sessionID, HttpServletRequest request, HttpServletResponse response){
boolean status = false;
SessionKey key = new WebSessionKey(sessionID,request,response);
try{
Session se = SecurityUtils.getSecurityManager().getSession(key);
Object obj = se.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
SimplePrincipalCollection coll = (SimplePrincipalCollection) obj;
return (UserPrivacy)coll.getPrimaryPrincipal();
}catch(Exception e){
e.printStackTrace();
}finally{
}
return null;
}
public static void authLogin(MyHttpServletRequestWrapper request, HttpServletResponse response, String username, String password){
//JSESSIONID為 :true 執(zhí)行登錄操作
logger.info("Shiro執(zhí)行登錄操作");
Subject subject = SecurityUtils.getSubject();
String sidVal = (String) subject.getSession().getId();
Cookie SIDCookie = CookieTool.setCookie(ShiroAutoAuthFilter.SESSIONID , sidVal, ShiroAutoAuthFilter.MAXAGE);
CookieTool.addCookie(response, SIDCookie, true);
request.putHeader(ShiroAutoAuthFilter.AUTHORIZATION, sidVal);
logger.info("subject生成的sessionID :"+sidVal);
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token);
}
}
MySessionManager類自定義的session獲取類
MySessionManager.java
/**
* @author by. 不笑貓丶
* @date 2018年12月12日
* 自定義session獲取
*/
public class MySessionManager extends DefaultWebSessionManager {
private static final Logger logger = LoggerFactory.getLogger(MySessionManager.class);
private static final String AUTHORIZATION = "Authorization";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public MySessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
//如果請(qǐng)求頭中有 Authorization 則其值為sessionId
if (!StringUtils.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
logger.info("Session管理器檢測(cè)到AUTHORIZATION token 使用此token作為Session");
return id;
} else {
//否則按默認(rèn)規(guī)則從cookie取sessionId
return super.getSessionId(request, response);
}
}
}
以上Shiro的配置和一些基本使用已經(jīng)全部搭建好院刁,可以開(kāi)始測(cè)試了。
測(cè)試之前粪狼,說(shuō)一點(diǎn)Dao層需注意的一點(diǎn)退腥,若要實(shí)現(xiàn)動(dòng)態(tài)的修改用戶權(quán)限范圍或者身份,需要更改身份時(shí)清除對(duì)應(yīng)的認(rèn)證緩存再榄,否則身份修改后狡刘,緩存還在的情況下,shiro會(huì)讀取緩存中之前的身份信息困鸥,所以可前端請(qǐng)求修改User身份時(shí)清除redis中的cache權(quán)限緩存信息嗅蔬,則下次用戶訪問(wèn)受保護(hù)的資源時(shí)會(huì)再次調(diào)用權(quán)限獲取方法 doGetAuthorizationInfo 從數(shù)據(jù)庫(kù)中獲取相應(yīng)權(quán)限和身份,此時(shí)獲取的數(shù)據(jù)為修改后的數(shù)據(jù)疾就,可以實(shí)現(xiàn)簡(jiǎn)單的用戶權(quán)限變更操作澜术。
部分Dao層代碼
/**
* 根據(jù) T 實(shí)體修改與column字段相匹配的數(shù)據(jù)信息
* @param userPrivacy 實(shí)體類
* @param column 字段名
* @param value 字段對(duì)應(yīng)值
* @return TRUE or FALSE
*/
@Override
public boolean UpdateToPrivacy(UserPrivacy userPrivacy, String column, String value){
Integer result = mapper.getUserPrivacyMapper().update(userPrivacy, new EntityWrapper<UserPrivacy>().eq(column, value));
if (result > 0){
if(userPrivacy.getRoleCode() != null){
RealmSecurityManager securityManager = (RealmSecurityManager) SecurityUtils.getSecurityManager();
//獲取MyShiroRealm
MyShiroRealm userRealm = (MyShiroRealm)securityManager.getRealms().iterator().next();
//執(zhí)行clearCache方法通過(guò)username清除緩存
userRealm.clearCache(value);
}
redisUtil.hset(0, "data_"+value,"update", 3, 604800);
return true;
}else {
return false;
}
}
Controller層
/**
* @author by. 不笑貓丶
* @date 2018年12月12日
*/
@RestController
@RequestMapping("/easeApi")
public class controllerTest {
/**
* 用戶登錄
* @param request
* @param username
* @return
*/
@RequestMapping("/auth/login")
public Object login() {
JSONObject jsonObject = new JSONObject();
Subject subject = SecurityUtils.getSubject();
subject.getSession().getId();
String username = "123456";
String password = "123456";
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
jsonObject.put("token", subject.getSession().getId());
jsonObject.put("msg", "登錄成功");
} catch (IncorrectCredentialsException e) {
jsonObject.put("msg", "密碼錯(cuò)誤");
} catch (LockedAccountException e) {
jsonObject.put("msg", "登錄失敗,該用戶已被凍結(jié)");
} catch (AuthenticationException e) {
jsonObject.put("msg", "該用戶不存在");
} catch (Exception e) {
e.printStackTrace();
}
return jsonObject;
}
@RequestMapping(value = "/authc/getUser")
//此處為了方便用1,2,3來(lái)代表用戶身份
@RequiresRoles(value = {"2"},logical = Logical.OR)
@RequiresPermissions("listen:high")
public Object getUser(){
return "Success";
}
@RequestMapping(value = "/authc/changeData")
@RequiresRoles(value = "1")
public Object changeData(){
UserPrivacy userPrivacy = new UserPrivacy();
userPrivacy.setRoleCode("2"); //修改用戶身份
String username = "123456";
boolean bol = userDao.UpdateToPrivacy(userPrivacy, "username", username);
return bol;
}
}
使用postMan測(cè)試
首先測(cè)試請(qǐng)求url 127.0.0.1:8080/easeApi/authc/changeData,得到以下結(jié)果
{
"msg": "未登錄",
"status": 3
}
因?yàn)樵谏厦鎐hangeData()方法中設(shè)置了@RequiresRoles(value = "1")猬腰,而一開(kāi)始用戶并沒(méi)有登錄所以拒絕訪問(wèn)被保護(hù)的資源
然后我們?cè)賮?lái)測(cè)試登錄操作,
請(qǐng)求url 127.0.0.1:8080/easeApi/auth/login,得到以下結(jié)果:
{
"msg": "登錄成功",
"token": "b4c00d4f-8724-4ff3-88f6-ba216a06e269"
}
用戶登錄后測(cè)試getUser(),由于初始登錄的用戶只擁有 "1" 這個(gè)身份瘪板,用戶以 "1" 的身份訪問(wèn)getUser()方法結(jié)果如下:
{
"msg": "權(quán)限不足",
"status": 3
}
結(jié)果顯示權(quán)限不足,因?yàn)橛脩舢?dāng)前的擁有的身份為 "1" 漆诽,而請(qǐng)求此方法所需要的權(quán)限為 "2" 以上侮攀,所以請(qǐng)求被攔截锣枝。
此時(shí)在執(zhí)行changeData()身份修改方法講用戶身份修改為"2"成功則返回true,得到結(jié)果如下
true
true既修改成功兰英。此時(shí)用戶擁有了 "2" 撇叁,這里再?gòu)?qiáng)調(diào)一下,在執(zhí)行changeData()方法的時(shí)候Dao層中會(huì)執(zhí)行這串代碼畦贸,清除對(duì)應(yīng)的username緩存信息陨闹。
if(userPrivacy.getRoleCode() != null){
RealmSecurityManager securityManager = (RealmSecurityManager) SecurityUtils.getSecurityManager();
MyShiroRealm userRealm = (MyShiroRealm)securityManager.getRealms().iterator().next();
userRealm.clearCache(username);
}
因?yàn)榫彺媲宄耍瑂hiro會(huì)再次執(zhí)行doGetAuthorizationInfo權(quán)限獲取方法從數(shù)據(jù)庫(kù)中獲取用戶權(quán)限信息薄坏,此時(shí)的數(shù)據(jù)為更新后的數(shù)據(jù)趋厉,從而實(shí)現(xiàn)用戶身份和權(quán)限范圍變更。
再次調(diào)用getUser()胶坠,得到以下結(jié)果
Success
大功告成君账!
文章沒(méi)有貼出前端Vue的代碼,可以使用axios發(fā)送AJAX請(qǐng)求即可完成同樣的操作沈善,注意做好Vue的跨域操作
在index.js中添加一下代碼即可
proxyTable: {
'/root': {
// 測(cè)試環(huán)境
target: 'http://127.0.0.1:8080/', // 接口域名
changeOrigin: true, //是否跨域
pathRewrite: {
'^/root': '' //需要rewrite重寫的,
}
},
},
以上就是SpringBoot+Shiro+Vue前后端分離開(kāi)發(fā)的全部項(xiàng)目實(shí)現(xiàn)乡数。本人小白難免出錯(cuò),請(qǐng)諒解闻牡。
我是不笑貓丶
一只愛(ài)編程的貓净赴。