spring整合shiro框架的實(shí)現(xiàn)步驟記錄
shiro
Shiro是Apache下的一個(gè)開源項(xiàng)目,我們稱之為Apache Shiro。它是一個(gè)很易用與Java項(xiàng)目的的安全框架,提供了認(rèn)證、授權(quán)、加密、會話管理,與spring Security 一樣都是做一個(gè)權(quán)限的安全框架,但是與Spring Security 相比,在于 Shiro 使用了比較簡單易懂易于使用的授權(quán)方式。shiro屬于輕量級框架,相對于security簡單的多,也沒有security那么復(fù)雜。更多詳細(xì)的介紹可以從它的官網(wǎng)上(http://shiro.apache.org/)基本可以了解到,她主要提供以下功能:
(1)Authentication(認(rèn)證)
?。?)Authorization(授權(quán))
?。?)Session Management(會話管理)
(4)Cryptography (加密)
首先,認(rèn)證服務(wù),也就是說通過她可以完成身份認(rèn)證,讓她去判斷用戶是否為真實(shí)的會員。
其次,授權(quán)服務(wù),說白了就是“訪問控制”服務(wù),也就是讓她來識別用戶擁有哪些權(quán)限。再說的白一點(diǎn),就是通過判斷用戶是什么角色,來賦予他哪些操作權(quán)限。
然后,還有會話管理服務(wù), 這時(shí)一個(gè)獨(dú)立的Session管理框架,和我們熟知的Http Session 不太一樣。
最后,她還提供了Cryptography(加密)服務(wù),封裝了很多密碼學(xué)的算法。
今天,我就不全說了,重點(diǎn)說下她的 會話管理功能, 其實(shí)這個(gè)也是幾乎所有的web應(yīng)該都會涉及到的。
在說shiro的會話管理服務(wù)前,先回顧下之前的會話管理我們是怎么做的。
1、最初我們是直接用的web服務(wù)器的 Http Session的機(jī)制, 也就是用戶第一次進(jìn)來的話,web容器會為這個(gè)請求創(chuàng)建一個(gè)session,然后把這個(gè)session存儲起來,通過將對應(yīng)的sessionId,作為cookie傳給客戶端,
如果客戶端再次向這個(gè)服務(wù)器發(fā)送請求的話,會自動(dòng)將這個(gè)sessionId帶過來, 然后web服務(wù)器會根據(jù)客戶端帶過來的 sessionId, 判斷其對于的session 是否還存在于內(nèi)存中(session是有過期時(shí)間的,可以在web.xml文件里面配置),如果找不到對應(yīng)的session了,說明已經(jīng)過了session失效時(shí)間,這時(shí)web服務(wù)器會再次為它創(chuàng)建一個(gè)session,然后和之前一樣,將這個(gè)新的sessionId傳給客戶端。
因此,我們可以通過這種機(jī)制,在程序里管理用戶的登錄會話,比如我們在用戶第一次登錄成功后,將用戶的基本信息存儲在session里(比如:session.setAttribute("user", "userInfo") ),下次用戶再次訪問的時(shí)候,我們根據(jù)獲取當(dāng)前session里的user信息
(session.getAttribute("user") ),來判斷用戶是否過期,如果獲取不到,那么提示用戶重新登錄。
2、第二種方式,就是我們將存儲信息的地方轉(zhuǎn)移到第三方介質(zhì)中,比如緩存里,memecache或者Redis都可以,這種方式主要是因?yàn)榉植际较到y(tǒng)的出現(xiàn)而采用的。
這種情況下,就需要我們自己生成sessionId了,一般我們會用一個(gè)定義好的前綴(user:login:token)再加上userid,或者時(shí)間戳都可以。 然后我們會將這個(gè)sessionId作為緩存的key, 用戶的信息作為value,存入緩存中,并設(shè)置失效時(shí)間:
jedisClient.set(tokenKey, JsonUtil.toJSONString(userInfo)); jedisClient.expire(tokenKey, TOKEN_LOSE_SECONDS);
我們還要將生成的這個(gè)tokenKey通過cookie傳到客戶端: CookieUtils.setCookie(request, response, "TT_TOKEN", tokenKey);
這樣,我們在用戶下次訪問的時(shí)候(定義一個(gè)攔截器),就可以從cookie里取出對應(yīng)的tokenKey,然后用這個(gè)tokenKey去到緩存里取相應(yīng)的值,如果獲取不到,說明這個(gè)key已經(jīng)失效了,提示用戶重新登錄。
注: tokenKey 很重要,她是連接緩存端和客戶端的樞紐。
3、最后一種就是我們shiro方式了,思路也類似,代碼挺簡單的,那我就直接上代碼吧:
1)、新建一個(gè) applicationContext-shiro.xml文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd"> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"></property> <property name="loginUrl" value="/loginPage"></property> <property name="unauthorizedUrl" value="/pages/unauthorized.jsp"/> <property name="filterChainDefinitions"> <value> /jcaptcha* = anon /logout = anon </value> </property> </bean> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"></property> <property name="arguments" ref="securityManager"></property> </bean> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"></property> <property name="sessionManager" ref="sessionManager"></property> </bean> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <property name="sessionDAO" ref="sessionDAO"></property> </bean> <bean id="sessionDAO" class="com.smart.core.shiro.MySessionDAO"></bean> //這個(gè)類是需要自己實(shí)現(xiàn)的 <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"></bean> </beans>
2)、在web.xml 里配置相應(yīng)的 filter:
<filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
3)寫一個(gè)實(shí)現(xiàn)類,繼承 AbstractSessionDAO,實(shí)現(xiàn)相應(yīng)的方法。
package com.jdd.core.shiro;
import com.smart.core.redis.RedisManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.SerializationUtils;
import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
public class MySessionDAO extends AbstractSessionDAO {
@Autowired
private RedisManager redisManager;
@Override
public void update(Session session) throws UnknownSessionException {
redisManager.set(SerializationUtils.serialize(session.getId().toString()), SerializationUtils.serialize(session));
redisManager.expire(SerializationUtils.serialize(session.getId().toString()), 60);
}
@Override
public void delete(Session session) {
redisManager.del(SerializationUtils.serialize(session.getId().toString()));
}
@Override
public Collection<Session> getActiveSessions() {
return new ArrayList<Session>();
}
@Override
protected Serializable doCreate(Session session) { //這就是第一次訪問的時(shí)候,創(chuàng)建sessionId
Serializable sid = this.generateSessionId(session);
assignSessionId(session, sid);
redisManager.set(SerializationUtils.serialize(session.getId().toString()), SerializationUtils.serialize(session));
redisManager.expire(SerializationUtils.serialize(session.getId().toString()), 60);
return sid;
}
@Override
protected Session doReadSession(Serializable serializable) { //這個(gè)方法其實(shí)就是通過sessionId讀取session,每讀一次,都要重新設(shè)置失效時(shí)間
byte[] aa = redisManager.get(SerializationUtils.serialize(serializable.toString()));
Session session = (Session) SerializationUtils.deserialize(aa);
redisManager.set(SerializationUtils.serialize(serializable.toString()), SerializationUtils.serialize(session));
redisManager.expire(SerializationUtils.serialize(serializable.toString()), 60);
return session;
}
}
4)下一步,我就是要在登錄成功之后的邏輯里,獲取到shiro 的session,然后將用戶信息設(shè)置進(jìn)去
package com.smart.controller;
import com.smart.pojo.User;
import com.smart.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private SecurityManager sm; //注入SecurityManager
private Logger logger = LoggerFactory.getLogger(UserController.class);
@RequestMapping(value = "/loginPage")
public String loginPage(){
return "user/userLogin";
}
@RequestMapping(value = "/userLogin", method = RequestMethod.POST)
public String userLogin(@RequestParam(value="name") String name, @RequestParam(value="pwd") String pwd, Model model){
logger.info("enter userLogin...");
User user = userService.getUserByNameAndPassword(name, pwd);
if(user == null){
logger.info("user is not exist...");
model.addAttribute("login_error", "用戶名或密碼錯(cuò)誤");
return "user/userLogin";
}
SecurityUtils.setSecurityManager(sm);
Subject currentUser = SecurityUtils.getSubject();
currentUser.getSession().setAttribute("LOGIN_USER", user);
return "redirect:/employee/list";
}
}
獲取當(dāng)前用戶,在shiro里是主題,然后獲取對應(yīng)的session,并將用戶信息設(shè)置進(jìn)去,是不是感覺有點(diǎn)像Http session的操作的樣子,哈哈。
5)、最后,定義一個(gè)springmvc 的攔截器,在攔截器里獲取相應(yīng)的session里的而用戶信息,如果獲取不到,則跳轉(zhuǎn)到登錄界面。
package com.smart.core.shiro;
import com.smart.pojo.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginInterceptor implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
@Autowired
private SecurityManager sm;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
logger.info("enter LoginInterceptor...");
HttpServletRequest request = httpServletRequest;
HttpServletResponse response = httpServletResponse;
logger.info("request uri===>"+request.getRequestURI()); //如果是登錄頁面的請求,則不攔截,否則會陷入死循環(huán)
if(request.getRequestURI().contains("loginPage") || request.getRequestURI().contains("userLogin")){
return true;
}else{
SecurityUtils.setSecurityManager(sm);
Subject currentUser = SecurityUtils.getSubject();
Object obj = currentUser.getSession().getAttribute("LOGIN_USER");
if(obj==null){
response.sendRedirect("http://localhost:8080/user/loginPage");
return false;
}else{
User user = (User)obj;
if(user==null || user.getName()==null){
response.sendRedirect("http://localhost:8080/user/loginPage");
return false;
}else{
return true;
}
}
}
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
到這里就基本結(jié)束了,如果你現(xiàn)在直接訪問主頁信息的話,它會自動(dòng)跳到登錄頁面。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
- 詳解spring整合shiro權(quán)限管理與數(shù)據(jù)庫設(shè)計(jì)
- SpringBoot整合Shiro實(shí)現(xiàn)登錄認(rèn)證的方法
- SpringBoot整合Shiro的代碼詳解
- spring boot整合Shiro實(shí)現(xiàn)單點(diǎn)登錄的示例代碼
- Apache shiro的簡單介紹與使用教程(與spring整合使用)
- spring boot整合redis實(shí)現(xiàn)shiro的分布式session共享的方法
- Spring 整合Shiro 并擴(kuò)展使用EL表達(dá)式的實(shí)例詳解
- Spring MVC整合Shiro權(quán)限控制的方法
相關(guān)文章
Spring Cloud LoadBalancer 負(fù)載均衡詳解
本文介紹了如何在Spring Cloud中使用SpringCloudLoadBalancer實(shí)現(xiàn)客戶端負(fù)載均衡,并詳細(xì)講解了輪詢策略和隨機(jī)策略的配置方法,此外,還提供了部署到云服務(wù)器并在多個(gè)實(shí)例之間進(jìn)行負(fù)載均衡的步驟,感興趣的朋友一起看看吧2025-02-02
Java使用POI導(dǎo)出大數(shù)據(jù)量Excel的方法
今天需要寫一個(gè)導(dǎo)出的Excel的功能,但是發(fā)現(xiàn)當(dāng)數(shù)據(jù)量到3萬條時(shí),列數(shù)在23列時(shí),內(nèi)存溢出,CPU使用100%,測試環(huán)境直接炸掉。小編給大家分享基于java使用POI導(dǎo)出大數(shù)據(jù)量Excel的方法,感興趣的朋友一起看看吧2019-11-11
Java 8 Stream Api 中的 map和 flatMap 操作方法
Java 8提供了非常好用的 Stream API ,可以很方便的操作集合。今天通過這篇文章給大家分享Java 8 Stream Api 中的 map和 flatMap 操作方法,需要的朋友可以參考下2019-11-11
解決springSecurity 使用默認(rèn)登陸界面登錄后無法跳轉(zhuǎn)問題
這篇文章主要介紹了解決springSecurity 使用默認(rèn)登陸界面登錄后無法跳轉(zhuǎn)問題,項(xiàng)目環(huán)境springboot下使用springSecurity 版本2.7.8,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2023-12-12
SpringBoot整合JWT框架,解決Token跨域驗(yàn)證問題
Json web token (JWT), 是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開放標(biāo)準(zhǔn)((RFC 7519).定義了一種簡潔的,自包含的方法用于通信雙方之間以JSON對象的形式安全的傳遞信息。2021-06-06
Java基于Graphics2D實(shí)現(xiàn)海報(bào)制作
這篇文章主要為大家詳細(xì)介紹了Java如何基于Graphics2D實(shí)現(xiàn)海報(bào)制作,并且支持自定義顏色,背景,logo,貼圖,感興趣的小伙伴可以了解一下2024-04-04
Java過濾器與監(jiān)聽器間區(qū)別與聯(lián)系
監(jiān)聽器是一個(gè)接口內(nèi)容由我們實(shí)現(xiàn),會在特定時(shí)間被調(diào)用,監(jiān)聽器用于監(jiān)聽web應(yīng)用中三大域?qū)ο?request,session,application),信息的創(chuàng)建,銷毀,增加,修改,刪除等動(dòng)作的發(fā)生,然后做出相應(yīng)的響應(yīng)處理2023-01-01

