Shiro是Apache下的一個(gè)開(kāi)源項(xiàng)目,我們稱之為Apache Shiro。它是一個(gè)很易用與Java項(xiàng)目的的安全框架,提供了認(rèn)證、授權(quán)、加密、會(huì)話管理,與spring Security 一樣都是做一個(gè)權(quán)限的安全框架,但是與Spring Security 相比,在于 Shiro 使用了比較簡(jiǎn)單易懂易于使用的授權(quán)方式。shiro屬于輕量級(jí)框架,相對(duì)于security簡(jiǎn)單的多,也沒(méi)有security那么復(fù)雜。更多詳細(xì)的介紹可以從它的官網(wǎng)上(http://shiro.apache.org/)基本可以了解到,她主要提供以下功能:
?。?)Authentication(認(rèn)證)
?。?)Authorization(授權(quán))
(3)Session Management(會(huì)話管理)
?。?)Cryptography (加密)
首先,認(rèn)證服務(wù),也就是說(shuō)通過(guò)她可以完成身份認(rèn)證,讓她去判斷用戶是否為真實(shí)的會(huì)員。
其次,授權(quán)服務(wù),說(shuō)白了就是“訪問(wèn)控制”服務(wù),也就是讓她來(lái)識(shí)別用戶擁有哪些權(quán)限。再說(shuō)的白一點(diǎn),就是通過(guò)判斷用戶是什么角色,來(lái)賦予他哪些操作權(quán)限。
然后,還有會(huì)話管理服務(wù), 這時(shí)一個(gè)獨(dú)立的Session管理框架,和我們熟知的Http Session 不太一樣。
最后,她還提供了Cryptography(加密)服務(wù),封裝了很多密碼學(xué)的算法。
今天,我就不全說(shuō)了,重點(diǎn)說(shuō)下她的 會(huì)話管理功能, 其實(shí)這個(gè)也是幾乎所有的web應(yīng)該都會(huì)涉及到的。
在說(shuō)shiro的會(huì)話管理服務(wù)前,先回顧下之前的會(huì)話管理我們是怎么做的。
1、最初我們是直接用的web服務(wù)器的 Http Session的機(jī)制, 也就是用戶第一次進(jìn)來(lái)的話,web容器會(huì)為這個(gè)請(qǐng)求創(chuàng)建一個(gè)session,然后把這個(gè)session存儲(chǔ)起來(lái),通過(guò)將對(duì)應(yīng)的sessionId,作為cookie傳給客戶端,
如果客戶端再次向這個(gè)服務(wù)器發(fā)送請(qǐng)求的話,會(huì)自動(dòng)將這個(gè)sessionId帶過(guò)來(lái), 然后web服務(wù)器會(huì)根據(jù)客戶端帶過(guò)來(lái)的 sessionId, 判斷其對(duì)于的session 是否還存在于內(nèi)存中(session是有過(guò)期時(shí)間的,可以在web.xml文件里面配置),如果找不到對(duì)應(yīng)的session了,說(shuō)明已經(jīng)過(guò)了session失效時(shí)間,這時(shí)web服務(wù)器會(huì)再次為它創(chuàng)建一個(gè)session,然后和之前一樣,將這個(gè)新的sessionId傳給客戶端。
因此,我們可以通過(guò)這種機(jī)制,在程序里管理用戶的登錄會(huì)話,比如我們?cè)谟脩舻谝淮蔚卿洺晒螅瑢⒂脩舻幕拘畔⒋鎯?chǔ)在session里(比如:session.setAttribute("user", "userInfo")
),下次用戶再次訪問(wèn)的時(shí)候,我們根據(jù)獲取當(dāng)前session里的user信息
(session.getAttribute("user")
),來(lái)判斷用戶是否過(guò)期,如果獲取不到,那么提示用戶重新登錄。
2、第二種方式,就是我們將存儲(chǔ)信息的地方轉(zhuǎn)移到第三方介質(zhì)中,比如緩存里,memecache或者Redis都可以,這種方式主要是因?yàn)榉植际较到y(tǒng)的出現(xiàn)而采用的。
這種情況下,就需要我們自己生成sessionId了,一般我們會(huì)用一個(gè)定義好的前綴(user:login:token
)再加上userid,或者時(shí)間戳都可以。 然后我們會(huì)將這個(gè)sessionId作為緩存的key, 用戶的信息作為value,存入緩存中,并設(shè)置失效時(shí)間:
1
2
|
jedisClient.set(tokenKey, JsonUtil.toJSONString(userInfo)); jedisClient.expire(tokenKey, TOKEN_LOSE_SECONDS); |
這樣,我們?cè)谟脩粝麓卧L問(wèn)的時(shí)候(定義一個(gè)攔截器),就可以從cookie里取出對(duì)應(yīng)的tokenKey,然后用這個(gè)tokenKey去到緩存里取相應(yīng)的值,如果獲取不到,說(shuō)明這個(gè)key已經(jīng)失效了,提示用戶重新登錄。
注: tokenKey 很重要,她是連接緩存端和客戶端的樞紐。
3、最后一種就是我們shiro方式了,思路也類(lèi)似,代碼挺簡(jiǎn)單的,那我就直接上代碼吧:
1)、新建一個(gè) applicationContext-shiro.xml文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
<? 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è)類(lèi)是需要自己實(shí)現(xiàn)的 < bean id = "cacheManager" class = "org.apache.shiro.cache.MemoryConstrainedCacheManager" ></ bean > </ beans > |
2)、在web.xml 里配置相應(yīng)的 filter:
1
2
3
4
5
6
7
8
9
10
11
12
|
< 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)寫(xiě)一個(gè)實(shí)現(xiàn)類(lèi),繼承 AbstractSessionDAO,實(shí)現(xiàn)相應(yīng)的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
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) { //這就是第一次訪問(wèn)的時(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í)就是通過(guò)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)去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
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里是主題,然后獲取對(duì)應(yīng)的session,并將用戶信息設(shè)置進(jìn)去,是不是感覺(jué)有點(diǎn)像Http session的操作的樣子,哈哈。
5)、最后,定義一個(gè)springmvc 的攔截器,在攔截器里獲取相應(yīng)的session里的而用戶信息,如果獲取不到,則跳轉(zhuǎn)到登錄界面。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
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()); //如果是登錄頁(yè)面的請(qǐng)求,則不攔截,否則會(huì)陷入死循環(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)在直接訪問(wèn)主頁(yè)信息的話,它會(huì)自動(dòng)跳到登錄頁(yè)面。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)服務(wù)器之家的支持。
原文鏈接:https://www.cnblogs.com/xiexin2015/p/9031785.html