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/)基本可以了解到,她主要提供以下功能:
?。?)Authentication(認(rèn)證)
?。?)Authorization(授權(quán))
?。?)Session Management(會話管理)
?。?)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ā)送請求的話,會自動將這個(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)在直接訪問主頁信息的話,它會自動跳到登錄頁面。
總結(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)文章
Java超詳細(xì)講解WebMvcConfigurer攔截器
這篇文章將用實(shí)例來和大家介紹一下WebMvcConfigurer攔截器。文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Java有一定的幫助,需要的可以參考一下2022-06-06SpringBoot實(shí)戰(zhàn)之實(shí)現(xiàn)結(jié)果的優(yōu)雅響應(yīng)案例詳解
這篇文章主要介紹了SpringBoot實(shí)戰(zhàn)之實(shí)現(xiàn)結(jié)果的優(yōu)雅響應(yīng)案例詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09Intellij IDEA命令行執(zhí)行java無法加載主類解決方案
這篇文章主要介紹了Intellij IDEA命令行執(zhí)行java無法加載主類解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09詳解Spring Boot 自定義PropertySourceLoader
這篇文章主要介紹了詳解Spring Boot 自定義PropertySourceLoader,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05java數(shù)據(jù)結(jié)構(gòu)基礎(chǔ):單鏈表與雙向鏈表
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)單鏈表、雙向鏈表的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08