Shiro是Apache下的一个开源项目,我们称之为Apache Shiro。它是一个很易用与Java项目的的安全框架,提供了认证、授权、加密、会话管理,与spring Security 一样都是做一个权限的安全框架,但是与Spring Security 相比,在于 Shiro 使用了比较简单易懂易于使用的授权方式。shiro属于轻量级框架,相对于security简单的多,也没有security那么复杂。更多详细的介绍可以从它的官网上(http://shiro.apache.org/)基本可以了解到,她主要提供以下功能:
(1)Authentication(认证)
(2)Authorization(授权)
(3)Session Management(会话管理)
(4)Cryptography (加密)
首先,认证服务,也就是说通过她可以完成身份认证,让她去判断用户是否为真实的会员。
其次,授权服务,说白了就是“访问控制”服务,也就是让她来识别用户拥有哪些权限。再说的白一点,就是通过判断用户是什么角色,来赋予他哪些操作权限。
然后,还有会话管理服务, 这时一个独立的Session管理框架,和我们熟知的Http Session 不太一样。
最后,她还提供了Cryptography(加密)服务,封装了很多密码学的算法。
今天,我就不全说了,重点说下她的 会话管理功能, 其实这个也是几乎所有的web应该都会涉及到的。
在说shiro的会话管理服务前,先回顾下之前的会话管理我们是怎么做的。
1、最初我们是直接用的web服务器的 Http Session的机制, 也就是用户第一次进来的话,web容器会为这个请求创建一个session,然后把这个session存储起来,通过将对应的sessionId,作为cookie传给客户端,
如果客户端再次向这个服务器发送请求的话,会自动将这个sessionId带过来, 然后web服务器会根据客户端带过来的 sessionId, 判断其对于的session 是否还存在于内存中(session是有过期时间的,可以在web.xml文件里面配置),如果找不到对应的session了,说明已经过了session失效时间,这时web服务器会再次为它创建一个session,然后和之前一样,将这个新的sessionId传给客户端。
因此,我们可以通过这种机制,在程序里管理用户的登录会话,比如我们在用户第一次登录成功后,将用户的基本信息存储在session里(比如:session.setAttribute("user", "userInfo")
),下次用户再次访问的时候,我们根据获取当前session里的user信息
(session.getAttribute("user")
),来判断用户是否过期,如果获取不到,那么提示用户重新登录。
2、第二种方式,就是我们将存储信息的地方转移到第三方介质中,比如缓存里,memecache或者Redis都可以,这种方式主要是因为分布式系统的出现而采用的。
这种情况下,就需要我们自己生成sessionId了,一般我们会用一个定义好的前缀(user:login:token
)再加上userid,或者时间戳都可以。 然后我们会将这个sessionId作为缓存的key, 用户的信息作为value,存入缓存中,并设置失效时间:
1
2
|
jedisClient.set(tokenKey, JsonUtil.toJSONString(userInfo)); jedisClient.expire(tokenKey, TOKEN_LOSE_SECONDS); |
这样,我们在用户下次访问的时候(定义一个拦截器),就可以从cookie里取出对应的tokenKey,然后用这个tokenKey去到缓存里取相应的值,如果获取不到,说明这个key已经失效了,提示用户重新登录。
注: tokenKey 很重要,她是连接缓存端和客户端的枢纽。
3、最后一种就是我们shiro方式了,思路也类似,代码挺简单的,那我就直接上代码吧:
1)、新建一个 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 > //这个类是需要自己实现的 < bean id = "cacheManager" class = "org.apache.shiro.cache.MemoryConstrainedCacheManager" ></ bean > </ beans > |
2)、在web.xml 里配置相应的 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)写一个实现类,继承 AbstractSessionDAO,实现相应的方法。
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) { //这就是第一次访问的时候,创建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) { //这个方法其实就是通过sessionId读取session,每读一次,都要重新设置失效时间 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,然后将用户信息设置进去
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" , "用户名或密码错误" ); return "user/userLogin" ; } SecurityUtils.setSecurityManager(sm); Subject currentUser = SecurityUtils.getSubject(); currentUser.getSession().setAttribute( "LOGIN_USER" , user); return "redirect:/employee/list" ; } } |
获取当前用户,在shiro里是主题,然后获取对应的session,并将用户信息设置进去,是不是感觉有点像Http session的操作的样子,哈哈。
5)、最后,定义一个springmvc 的拦截器,在拦截器里获取相应的session里的而用户信息,如果获取不到,则跳转到登录界面。
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()); //如果是登录页面的请求,则不拦截,否则会陷入死循环 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 { } } |
到这里就基本结束了,如果你现在直接访问主页信息的话,它会自动跳到登录页面。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:https://www.cnblogs.com/xiexin2015/p/9031785.html