Shiro 整合SpringMVC 并实现权限管理,登录和注销

时间:2023-01-03 13:21:45

Shiro 整合SpringMVC 并且实现权限管理,登录和注销

Apache Shiro是Java的一个安全框架。目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的Shiro就足够了。

  因为我总结的是使用SpringMVC和Apache Shiro整合,注重的是整合和使用,至于基础,我这里就不细说了。我使用的是maven进行项目的构建,对于非maven的项目只要把这些JAR包下载下来放到相应的位置即可。因为这个项目是整合Spring的,所以除了Apache shiro的JAR之外,我们还需要shiro-web和shiro-spring的的JAR,下面是所需要的所有shiro架包,至于其他的架包,像缓存的架包,Spring和SpringMVC的架包等等还是平时那些通用JAR,没有多余的。

<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>

将JAR都准备好了之后,我们就可以开始正式搭建了。下面就分步骤来创建

1.

一:首先创建spring的配置文件,位置都在resource中(非maven的项目可以放到classpath或者是WEB-INF下面,只要保证最后编译之后能在classpath下即可),配置文件为spring-context.xml.

二:创建Apache Shiro的配置文件,名字是spring-context-shiro.xml,我们只需要和spring的配置文件放在同一级就可以了。

三:还有一个配置文件是springmvc的,配置文件是spring-mvc。前面两个文件都是以spring-context*开头是有原因的,因为这样我们就可以在web.xml中设置配置文件的时候,直接使用通配符扫描前两个但是又可以不扫描springmvc的配置文件

这是在web.xml里面配置:

<!-- 配置spring容器的路径 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:/spring-context-*.xml</param-value>
</context-param>
<!-- 对spring开始监听 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

除了spring的配置,还有一个配置是非常重要的:shiroFilter。对于初次配置shiro的同学经常遇到一个问题:问题大概讲的是shiroFilter找不到,但是我们明明在web.xml和spring-context-shiro配置文件里面配置了呀,怎么回事?这是因为这个shiroFilter名字两边需要一致!!!(是不是很坑,但是其实是可以配置的,只是一般人不知道,这个后面讲)

  <filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

3.

除了在web.xml中设置spring和spring-shiro配置文件位置之外,我们还需要在web.xml中设置spring-mvc的位置:

<!-- MVC Servlet
设置springmvc的Servlet
-->
<servlet>
<servlet-name>springServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

4

在spring-context配置文件中,还有一个是需要配置-cacheManager,因为shiro的session是自己实现的,所以我们还需要一个缓存框架,所以在spring的配置文件一定要注意配置哦,用的是ehcache

   <!-- 缓存 -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:${ehcache.file}"></property>
</bean>

Ehcache的maven地址:

 <dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.9</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>

5

在项目中重点还是配置spring-context-shiro.xml:先把配置的贴出来,然后讲一下这几个配置的意义:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" 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"
default-lazy-init="true"> <description>Shiro Configuration</description> <!-- 加载配置属性文件 -->
<context:property-placeholder ignore-unresolvable="true" location="classpath:yonyou.properties" /> <!-- Shiro权限过滤过滤器定义 -->
<bean name="shiroFilterChainDefinitions" class="java.lang.String">
<constructor-arg>
<value>
/static/** = anon
/userfiles/** = anon
${adminPath}/login = authc
${adminPath}/logout = logout
${adminPath}/** = user
</value>
</constructor-arg>
</bean> <!-- 安全认证过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" /><!--
<property name="loginUrl" value="${adminPath}/login" />
<property name="successUrl" value="${adminPath}?login" />
<property name="filters">
<map>
<entry key="cas" value-ref="casFilter"/>
<entry key="authc" value-ref="formAuthenticationFilter"/>
</map>
</property>
<property name="filterChainDefinitions">
<ref bean="shiroFilterChainDefinitions"/>
</property>
</bean> <!-- 定义Shiro安全管理配置 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="systemAuthorizingRealm" />
<property name="sessionManager" ref="sessionManager" />
<property name="cacheManager" ref="shiroCacheManager" />
</bean> <!-- 自定义会话管理配置 -->
<bean id="sessionManager" class="com.yonyou.hotusm.common.security.session.SessionManager">
<property name="sessionDAO" ref="sessionDAO"/> <!-- 会话超时时间,单位:毫秒 -->
<property name="globalSessionTimeout" value="${session.sessionTimeout}"/> <!-- 定时清理失效会话, 清理用户直接关闭浏览器造成的孤立会话 -->
<property name="sessionValidationInterval" value="${session.sessionTimeoutClean}"/>
<!-- <property name="sessionValidationSchedulerEnabled" value="false"/> -->
<property name="sessionValidationSchedulerEnabled" value="true"/> <property name="sessionIdCookie" ref="sessionIdCookie"/>
<property name="sessionIdCookieEnabled" value="true"/>
</bean> <!-- 指定本系统SESSIONID, 默认为: JSESSIONID 问题: 与SERVLET容器名冲突, 如JETTY, TOMCAT 等默认JSESSIONID,
当跳出SHIRO SERVLET时如ERROR-PAGE容器会为JSESSIONID重新分配值导致登录会话丢失! -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg name="name" value="hotusm.session.id"/>
</bean> <bean id="sessionDAO" class="com.yonyou.hotusm.common.security.session.CacheSessionDAO">
<property name="sessionIdGenerator" ref="idGen" />
<property name="activeSessionsCacheName" value="activeSessionsCache" />
<property name="cacheManager" ref="shiroCacheManager" />
</bean> <!-- 定义授权缓存管理器 -->
<!-- <bean id="shiroCacheManager" class="com.thinkgem.jeesite.common.security.shiro.cache.SessionCacheManager" /> -->
<bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManager" ref="cacheManager"/>
</bean> <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- AOP式方法级权限检查 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean> </beans>

这里从上往下进行解释:
1.shiroFilterChainDefinitions

可以看到类型是String,String内部的各个字符串是使用"\n\t"进行换行。这里的每一行代表了一个路由,而后面的anno,user等等,也就是相对应的Filter(这块我们是可以自己定义的,后面会讲,${adminPath} 是我在配置文件里面配置的路径而已,完全可以根据自己的路由进行设置。shiroFilterChainDefinitions最主要是在shiroFilter中作为一个参数注入。

===============权限过滤器及配置释义=======================

anon   org.apache.shiro.web.filter.authc.AnonymousFilter

authc  org.apache.shiro.web.filter.authc.FormAuthenticationFilter

authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter

perms  org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter

port   org.apache.shiro.web.filter.authz.PortFilter

rest   org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter

roles  org.apache.shiro.web.filter.authz.RolesAuthorizationFilter

ssl    org.apache.shiro.web.filter.authz.SslFilter

user   org.apache.shiro.web.filter.authc.UserFilter

logout org.apache.shiro.web.filter.authc.LogoutFilter

anon:例子/admins/**=anon 没有参数,表示可以匿名使用。

authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数

roles:例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。

perms:例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。

rest:例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。

port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString

是你访问的url里的?后面的参数。

authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证

ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https

user:例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查

2.重点来了:shiroFilter(ShiroFilterFactoryBean),这里要非常小心!! 这里的bean的名字一定要和web.xml里面的那个Filter名字相同,具体可以见下面的源码:

DelegatingFilterProxy.java:
@Override
protected void initFilterBean() throws ServletException {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
// If no target bean name specified, use filter name.
if (this.targetBeanName == null) {
this.targetBeanName = getFilterName();
}
// Fetch Spring root application context and initialize the delegate early,
// if possible. If the root application context will be started after this
// filter proxy, we'll have to resort to lazy initialization.
WebApplicationContext wac = findWebApplicationContext();
if (wac != null) {
this.delegate = initDelegate(wac);
}
}
}
}

还记得我们web.xml里面配置的那个Filter吗, 其实我们配置的Filter只不过是起到一个代理的作用,那么它代理谁呢? 它也不能知道,它所能做的就是根据targetBeanName去容器中获取bean(这个bean是实现了Filter接口的),其中的targetBeanName就是bean的名称,如果没有设置的话,那么就默认使用的Filter名称。所以说前面说过的必须相同是不正确的,你只需要在Filter中设置targetBeanName和spring-context-shiro配置文件中ShiroFilterFactoryBean的bean名称一样即可。

除了上面需要注意的几个点之外,ShiroFilterFactoryBean还有一些属性:unauthorizedUrl,系统未认证时跳转的页面,loginUrl登录页面,successUrl登录成功的页面,filter属性就是和前面的shiroFilterChainDefinitions对应的。同时支持自定义,并且配置路由:像<entry key="outdate" value-ref="sessionOutDateFilter"/>这样的。最底层是过滤器,下面是我实现的一个filter:

package com.yonyou.kms.common.security.shiro.session;

import java.io.PrintWriter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.apache.shiro.web.servlet.AdviceFilter; import com.yonyou.kms.modules.sys.security.SystemAuthorizingRealm.Principal;
import com.yonyou.kms.modules.sys.utils.UserUtils; /**
*
* 自定义filter
* @author Hotusm
*
*/
public class SessionOutDateFilter extends AdviceFilter{ private String redirectUrl="http://url/portal";//session 失效之后需要跳转的页面
private String platformUrl="http://url/kms/a/login";
//排除这个链接 其他的链接都会进行拦截
private String loginUrl="/kms/a/login";
private String frontUrl="cms/f";
private String uploadUrl="cms/article/plupload";
private String appUrl="a/app"; protected boolean preHandle(ServletRequest request, ServletResponse response){
Principal principal = UserUtils.getPrincipal();
HttpServletRequest req=(HttpServletRequest) request;
String uri=req.getRequestURI();
if(checkUrl(uri, loginUrl,frontUrl,uploadUrl,appUrl)|(principal!=null&&!principal.isMobileLogin())){ return true;
} try {
issueRedirect(request,response,redirectUrl);
} catch (Exception e) {
e.printStackTrace();
}
return false;
} protected void issueRedirect(ServletRequest request, ServletResponse response, String redirectUrl)
throws Exception
{ String url="<a href="+redirectUrl+" target=\"_blank\" onclick=\"custom_close()\">重新登录<a/> ";
String platform="<a href="+platformUrl+" target=\"_blank\" onclick=\"custom_close()\">直接登录<a/> "; HttpServletResponse resp=(HttpServletResponse) response;
HttpServletRequest req=(HttpServletRequest) request;
response.setContentType("text/html;charset=UTF-8");
PrintWriter out=resp.getWriter();
out.print("<script language='javascript'>");
out.print("function custom_close(){" +
"self.opener=null;" +
"self.close();}");
out.print("</script>");
out.print("没有权限或者验证信息过期,请点击"+url+"登录portal<br/>");
out.print("直接登录"+platform);
} public String getRedirectUrl() {
return redirectUrl;
} public void setRedirectUrl(String redirectUrl) {
this.redirectUrl = redirectUrl;
} public String getLoginUrl() {
return loginUrl;
} public void setLoginUrl(String loginUrl) {
this.loginUrl = loginUrl;
} /**
* 排除一些url不进行拦截
* @param targetUrl
* @param urls
* @return
*/
private boolean checkUrl(String targetUrl,String ...urls){
for(int i=0;i<urls.length;i++){
if(targetUrl.contains(urls[i])){
return true;
}
} return false;
}
}

这个和springmvc的拦截器是相同的用法,返回true则表示验证通过(后面的逻辑继续执行),返回false就表示验证不通过。

最后在shiroFilter的filters进行配置我们自定义的bean:

<property name="filters">
<map>
<entry key="outdate" value-ref="sessionOutDateFilter"/>
</map>
</property>
这个sessionOutDateFilter我们需要注入(这里省略)。最后我们就将可以将这些东西加到shiroFilterChainDefinitions中去:
   <bean name="shiroFilterChainDefinitions" class="java.lang.String">
<constructor-arg>
<value>
......
${adminPath}/** = outdate
.....
</value>
</constructor-arg>
</bean>

这样我们自己定义的叫做outdata的路由会拦截${adminPath}下的所以路径,并且进行验证。

3.

SecurityManager

它和我们前面讲的ShiroFilterFactoryBean的关系形象的将就是ShiroFilterFactoryBean是一个路由规则配置仓库和代理类,其实真正的逻辑都是在SecurityManager中进行的,下面来进行详讲SecurityManager的依赖类。

一:realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源,下面是我重写的realm:

package com.yonyou.hotusm.module.sys.security;

import com.yonyou.hotusm.common.utils.Encodes;
import com.yonyou.hotusm.module.sys.dao.UserDao;
import com.yonyou.hotusm.module.sys.entity.User;
import com.yonyou.hotusm.module.sys.service.UserService;
import com.yonyou.hotusm.module.sys.util.UserUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import java.io.Serializable; @Service("systemAuthorizingRealm")
public class SystemAuthorizingRealm extends AuthorizingRealm implements InitializingBean{ @Autowired
private UserDao userDao; @Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
info.addStringPermission("sys:manager");
info.addStringPermission("user");
System.out.println("开始授权");
return info;
} @Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken=(UsernamePasswordToken) token;
String username=upToken.getUsername();
User user=new User();
user.setLoginName(username);
user=userDao.get(user); if(user!=null){
byte[] salt = Encodes.decodeHex(user.getPassword().substring(0,16));
return new SimpleAuthenticationInfo(username,
user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName());
}else{
throw new UnauthenticatedException();
}
} public static class Principal implements Serializable { private static final long serialVersionUID = 1L; private String id; // 编号
private String loginName; // 登录名
private String name; // 姓名 public Principal(User user) {
this.id = user.getId();
this.loginName = user.getLoginName();
this.name = user.getName();
} public String getId() {
return id;
} public String getLoginName() {
return loginName;
} public String getName() {
return name;
} /**
* 获取SESSIONID
*/
public String getSessionid() {
try{
return (String) UserUtils.getSession().getId();
}catch (Exception e) {
return "";
}
} @Override
public String toString() {
return id;
} }
//在bean初始化完成以后 设置校验的规则
public void afterPropertiesSet() throws Exception {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(UserService.HASH_ALGORITHM);
matcher.setHashIterations(UserService.HASH_INTERATIONS);
setCredentialsMatcher(matcher); } }

其他的几个类在这里不是重点,重要的是看这里面的逻辑。其中最重要的是doGetAuthorizationInfo和doGetAuthenticationInfo以及afterPropertiesSet这三个方法,doGetAuthorizationInfo是对当前的用户进行授权的,至于授权的时期,就是当用户需要验证的时候(框架进行回调),我这里只是简单的写死了,但是在实际项目开发中,我们一般会将权限存放在数据表中,所以真实情况是先到数据库中查出一个集合,然后迭代授权。

doGetAuthenticationInfo对于的是对用户验证,主要的一个点在于我们最后返回的那个SimpleAuthenticationInfo,这个是加密的策略,这里的密码是密文的(根据loginName数据中取得),下面是密码的加密策略:

//为明文密码加密
public String encryptionPassword(String plainPassword){
byte[] salt = Digests.generateSalt(SALT_SIZE); //SALT_SIZE=8
byte[] hashPassword = Digests.sha1(plainPassword.getBytes(), salt, HASH_INTERATIONS); //HASH_INTERATIONS=1024
return Encodes.encodeHex(salt)+Encodes.encodeHex(hashPassword); }

我这里是生成了了16位的salt,然后用来加密明文,最后两个加起来存入到数据中。根据上面说的,所以看到我们doGetAuthenticationInfo返回的是分开的两部分。这里也需要注意,这个密码最后的校验我们做的,而是框架!我们只是提供了校验类供它回调(下面我们使用的是默认的校验类,我们也可以自定义):

  //在bean初始化完成以后  设置校验的规则
public void afterPropertiesSet() throws Exception {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(UserService.HASH_ALGORITHM);
matcher.setHashIterations(UserService.HASH_INTERATIONS);
setCredentialsMatcher(matcher);

,那么在密码进行验证的时候,就会调用HashedCredentialsMatcher的

@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenHashedCredentials = hashProvidedCredentials(token, info);
Object accountCredentials = getCredentials(info);
return equals(tokenHashedCredentials, accountCredentials);
}

方法,这个Info就是我们前面方法doGetAuthenticationInfo提供的,至于另外的一个Token,后面会讲(也是一个方法提供的)。

4

下面就是讲解SessionManager,因为Shiro有自己的一套session体系,有sessionManager就不奇怪了,sessionManager主要职责是管理session的创建和删除,特别提一下,sessionManager对session的操作,其实只是调用了sessionDAO,然再加上自己的一些操作。

看源码:


public class DefaultSessionManager extends AbstractValidatingSessionManager implements CacheManagerAware {

    //TODO - complete JavaDoc

    private static final Logger log = LoggerFactory.getLogger(DefaultSessionManager.class);

    private SessionFactory sessionFactory;

    protected SessionDAO sessionDAO;  //todo - move SessionDAO up to AbstractValidatingSessionManager?

    private CacheManager cacheManager;

    private boolean deleteInvalidSessions;

    public DefaultSessionManager() {
this.deleteInvalidSessions = true;
this.sessionFactory = new SimpleSessionFactory();
this.sessionDAO = new MemorySessionDAO();
}
.......
protected void create(Session session) {
if (log.isDebugEnabled()) {
log.debug("Creating new EIS record for new session instance [" + session + "]");
}
sessionDAO.create(session);
}

这上面的源码中就知道SessionManager就是对SessionDAO进行了代理的作用。

我们就明白了sessionManager依赖sessionDAO(后面实现自己的SessionDAO需要注入到SessionManager中),下面是自己实现的sessionManager:


package com.yonyou.hotusm.common.security.session;

import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SessionContext;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.session.mgt.SimpleSession;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils; import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.util.Collection;
import java.util.Date; /***
*
* @author Hotusm
* v2015-11-04
*/
public class SessionManager extends DefaultWebSessionManager{ /*
*DefaultWebSessionManager 实现了DefaultSessionManager的功能 并在其上实现了web的功能
* 也就是在上面实现了将SessionId 存到了Cookie中
* */
@Override
protected Serializable getSessionId(ServletRequest request,
ServletResponse response) { String sid=request.getParameter("_sid");
if(org.apache.commons.lang.StringUtils.isNotBlank(sid)){
if(WebUtils.isTrue(request, "_cookie")){
HttpServletRequest req=(HttpServletRequest) request;
HttpServletResponse resp=(HttpServletResponse) response;
Cookie template=getSessionIdCookie();
Cookie cookie=new SimpleCookie(template);
cookie.setValue(sid);
cookie.saveTo(req, resp);
}
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sid);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return sid;
} return super.getSessionId(request, response);
} @Override
protected Session doCreateSession(SessionContext context) {
try {
return super.doCreateSession(context);
} catch (Exception e) {
return null;
} } @Override
protected Session newSessionInstance(SessionContext context) { Session session=super.newSessionInstance(context);
session.setTimeout(getGlobalSessionTimeout());
return session;
} @Override
protected Session retrieveSession(SessionKey sessionKey)
throws UnknownSessionException { try {
return super.retrieveSession(sessionKey);
} catch (Exception e) {
//获取不到SESSION不报错
return null;
} } @Override
public void validateSessions() {
super.validateSessions();
} @Override
public Session start(SessionContext context) {
try {
return super.start(context);
} catch (Exception e) {
SimpleSession session=new SimpleSession();
session.setId(0);
return session;
}
} @Override
public Date getStartTimestamp(SessionKey key) {
try {
return super.getStartTimestamp(key);
} catch (Exception e) {
return null;
} } @Override
public Date getLastAccessTime(SessionKey key) {
try {
return super.getLastAccessTime(key);
} catch (Exception e) {
return null;
}
} @Override
public long getTimeout(SessionKey key) throws InvalidSessionException {
try {
return super.getTimeout(key);
} catch (Exception e) {
return 0;
} } @Override
public void setTimeout(SessionKey key, long maxIdleTimeInMillis)
throws InvalidSessionException {
try {
super.setTimeout(key, maxIdleTimeInMillis);
} catch (Exception e) { }
} @Override
public void touch(SessionKey key) throws InvalidSessionException {
try {
super.touch(key);
} catch (Exception e) {
} } @Override
public String getHost(SessionKey key) {
try {
return super.getHost(key);
} catch (Exception e) {
return null;
} } @Override
public Collection<Object> getAttributeKeys(SessionKey key) {
try {
return super.getAttributeKeys(key);
} catch (Exception e) {
return null;
} } @Override
public Object getAttribute(SessionKey sessionKey, Object attributeKey)
throws InvalidSessionException {
try {
return super.getAttribute(sessionKey, attributeKey);
} catch (Exception e) {
return null;
} } @Override
public Object removeAttribute(SessionKey sessionKey, Object attributeKey)
throws InvalidSessionException {
try {
return super.removeAttribute(sessionKey, attributeKey);
} catch (Exception e) {
return null;
}
//
} @Override
public void stop(SessionKey key) throws InvalidSessionException {
try {
super.stop(key);
} catch (Exception e) { } } @Override
public void checkValid(SessionKey key) throws InvalidSessionException {
try {
super.checkValid(key);
} catch (Exception e) {
}
}
}

上面就是对session的操作.

5

还有就是sessionDAO了,这个sessionDAO才是真正对session操作的bean:


package com.yonyou.hotusm.common.security.session;

import com.google.common.collect.Sets;
import com.yonyou.hotusm.common.config.Global;
import com.yonyou.hotusm.common.web.Servlets;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO; import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.Collection;
import java.util.Set; /**
* @author Hotusm
* v-2015-10-28
*/
public class CacheSessionDAO extends EnterpriseCacheSessionDAO implements SessionDAO { @Override
protected Serializable doCreate(Session session) {
HttpServletRequest request = Servlets.getRequest();
if (request != null) {
String uri = request.getRequestURI();
if (Servlets.isStaticFile(uri)) {
return null;
}
}
super.doCreate(session);
//System.out.println("doCreate:"+" sessionId"+session.getId());
return session.getId();
} @Override
public Session readSession(Serializable sessionId) throws UnknownSessionException {
//System.out.println("readSession:"+" sessionId"+sessionId);
//System.out.println();
try {
Session s = null;
HttpServletRequest request = Servlets.getRequest();
if (request != null) {
String uri = request.getRequestURI();
if (Servlets.isStaticFile(uri)) {
return null;
}
s = (Session) request.getAttribute("session_" + sessionId);
} if (s != null) {
return s;
}
Session session = super.readSession(sessionId);
if (request != null && session != null) {
request.setAttribute("session_" + sessionId, session);
}
return session;
} catch (Exception e) { return null;
} } @Override
protected Session doReadSession(Serializable sessionId) {
//System.out.println("doReadSession:"+" sessionId"+sessionId);
return super.doReadSession(sessionId);
} @Override
protected void doUpdate(Session session) {
// System.out.println("doUpdate"+" sessionId"+session.getId());
if (session == null || session.getId() == null) {
return;
}
HttpServletRequest request = Servlets.getRequest();
if (request != null) {
String uri = request.getRequestURI();
if (Servlets.isStaticFile(uri)) {
return;
}
if (org.apache.commons.lang.StringUtils.startsWith(uri, Global.getConfig("web.view.prefix"))
&& org.apache.commons.lang.StringUtils.endsWith(uri, Global.getConfig("web.view.suffix"))) {
return;
}
//手动控制不更新session
String updateSession = request.getParameter("updateSession");
if (Global.FALSE.equals(updateSession) || Global.NO.equals(updateSession)) {
return;
}
}
super.doUpdate(session);
} @Override
protected void doDelete(Session session) {
//System.out.println("doDelete");
if (session == null || session.getId() == null) {
return;
}
super.doUpdate(session);
} public Collection<Session> getActiveSessions(boolean includeLeave) { return null;
} public Collection<Session> getActiveSessions(boolean includeLeave,
Object principal, Session filterSession) { if (includeLeave && principal == null) {
return this.getActiveSessions();
}
Set<Session> sessions = Sets.newHashSet();
for (Session session : getActiveSessions()) {
boolean isActiveSession = true; }
return null;
} }

6.

看sessionDAO还有一个idGen依赖bean,指的是sessionId值的生成策略,这个bean也是自己定义的,但是需要继承SessionIdGenerator:

public class IdGen implements SessionIdGenerator{

    private static SecureRandom secureRandom;

    /**
* 封装JDK自带的UUID,通过random生成
*/
public static String uuid(){
return UUID.randomUUID().toString().replace("-", "");
} public static long randomLong(){
return Math.abs(secureRandom.nextLong());
} public Serializable generateId(Session session) {
return IdGen.uuid();
} }

返回的就是session的值,至于shiroCacheManager就是session缓存的储存位置(它依赖的是我们在spring-context定义的cacheManager)。

3.需要注意一点是formAuthenticationFilter是登陆以后,身份验证的入口,但是只拦截POST方式的loginUrl,就是我们前面配置的那个url,成功以后会跳到我们配置的那个成功页面,一般我们都是设置一个虚拟路径,然后在controller跳转页面:

/**
* 登录成功,进入管理首页
*/
@RequiresPermissions("user")
@RequestMapping(value = "${adminPath}")
public String index(HttpServletRequest request, HttpServletResponse response) {
Principal principal = UserUtils.getPrincipal();
List<String> str=commentService.commentList(null);
//System.out.println(JsonMapper.toJsonString(str));
// 登录成功后,验证码计算器清零
isValidateCodeLogin(principal.getLoginName(), false, true); if (logger.isDebugEnabled()){ logger.debug("show index, active session size: {}", sessionDAO.getActiveSessions(false).size());
} // 如果已登录,再次访问主页,则退出原账号。
if (Global.TRUE.equals(Global.getConfig("notAllowRefreshIndex"))){ String logined = CookieUtils.getCookie(request, "LOGINED");
if (org.apache.commons.lang3.StringUtils.isBlank(logined) || "false".equals(logined)){ CookieUtils.setCookie(response, "LOGINED", "true");
}else if (org.apache.commons.lang3.StringUtils.equals(logined, "true")){
UserUtils.getSubject().logout(); return "redirect:" + adminPath + "/login";
}
}
/
return "modules/sys/sysIndex";
}

下面是authc对应的那个filter的代码,


@Service
public class FormAuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter { public static final String DEFAULT_CAPTCHA_PARAM = "validateCode";
public static final String DEFAULT_MOBILE_PARAM = "mobileLogin";
public static final String DEFAULT_MESSAGE_PARAM = "message"; private String captchaParam = DEFAULT_CAPTCHA_PARAM;
private String mobileLoginParam = DEFAULT_MOBILE_PARAM;
private String messageParam = DEFAULT_MESSAGE_PARAM; @Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { String username = getUsername(request);
String password = getPassword(request);boolean rememberMe = isRememberMe(request); String host = StringUtils.getRemoteAddr((HttpServletRequest)request);
boolean mobile = isMobileLogin(request);
return new UsernamePasswordToken(username, password.toCharArray(), rememberMe, host, mobile);
//end
} public String getCaptchaParam() {
return captchaParam;
} protected String getCaptcha(ServletRequest request) {
return WebUtils.getCleanParam(request, getCaptchaParam());
} public String getMobileLoginParam() {
return mobileLoginParam;
} protected boolean isMobileLogin(ServletRequest request) {
return WebUtils.isTrue(request, getMobileLoginParam());
} public String getMessageParam() {
return messageParam;
} /**
* 登录成功之后跳转URL
*/
@Override
public String getSuccessUrl() {
return super.getSuccessUrl();
} @Override
protected void issueSuccessRedirect(ServletRequest request,
ServletResponse response) throws Exception {
// Principal p = UserUtils.getPrincipal();
// if (p != null && !p.isMobileLogin()){
WebUtils.issueRedirect(request, response, getSuccessUrl(), null, true);
// }else{
// super.issueSuccessRedirect(request, response);
// }
} /**
* 登录失败调用事件
*/
@Override
protected boolean onLoginFailure(AuthenticationToken token,
AuthenticationException e, ServletRequest request, ServletResponse response) {
String className = e.getClass().getName(), message = "";
if (IncorrectCredentialsException.class.getName().equals(className)
|| UnknownAccountException.class.getName().equals(className)){
message = "用户或密码错误, 请重试.";
}
else if (e.getMessage() != null && org.apache.commons.lang3.StringUtils.startsWith(e.getMessage(), "msg:")){
message = org.apache.commons.lang3.StringUtils.replace(e.getMessage(), "msg:", "");
}
else{
message = "系统出现点问题,请稍后再试!";
e.printStackTrace(); // 输出到控制台
}
request.setAttribute(getFailureKeyAttribute(), className);
request.setAttribute(getMessageParam(), message);
return true;
} }

这里的Token就是我们前面所讲的Info一起来做明文和密文进行校验的。

经过上面的一些操作,shiro登录和授权就可以做好了,对于退出,我们只要设置退出按钮的链接地址是我们前面filterChainDefinitions配置的路径就可以了,我的是: ${adminPath}/logout = logout;

具体的代码在github:https://github.com/Housum/blog.git

 

Shiro 整合SpringMVC 并实现权限管理,登录和注销的更多相关文章

  1. Shiro 整合SpringMVC 并且实现权限管理,登录和注销

    Apache Shiro是Java的一个安全框架.目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大 ...

  2. Shiro 整合SpringMVC 并且实现权限管理

    Apache Shiro是Java的一个安全框架.目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大 ...

  3. (十二)整合 Shiro 框架,实现用户权限管理

    整合 Shiro 框架,实现用户权限管理 1.Shiro简介 1.1 基础概念 1.2 核心角色 1.3 核心理念 2.SpringBoot整合Shiro 2.1 核心依赖 2.2 Shiro核心配置 ...

  4. SpringBoot2&period;0 整合 Shiro 框架,实现用户权限管理

    本文源码:GitHub·点这里 || GitEE·点这里 一.Shiro简介 1.基础概念 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理.作为一款安全 ...

  5. shiro整合springmvc

    说明   代码及部分相关资料根据慕课网Mark老师的视频进行整理   其他资料: shiro官网 流程 配置 1) 配置web.xml整合shiro 把shiro整合到springMVC实质上是在we ...

  6. Shiro集成SSM基于URL权限管理&lpar;一&rpar;

    学习了shiro之后,我们就可以说尝试把shiro加入ssm中,并做一套基于URL的权限管理. 其他的准备工作就不多说了,直接动手操作,看到效果再去理解. 表结构 执行如下,数据库名字可以自行修改,不 ...

  7. Spring boot 入门(四):集成 Shiro 实现登陆认证和权限管理

    本文是接着上篇博客写的:Spring boot 入门(三):SpringBoot 集成结合 AdminLTE(Freemarker),利用 generate 自动生成代码,利用 DataTable 和 ...

  8. springBoot整合spring security实现权限管理&lpar;单体应用版&rpar;--筑基初期

    写在前面 在前面的学习当中,我们对spring security有了一个小小的认识,接下来我们整合目前的主流框架springBoot,实现权限的管理. 在这之前,假定你已经了解了基于资源的权限管理模型 ...

  9. MVC设计模式实现权限管理登录,超详细

    功能实现:在页面输入给定的用户名之一,可以显示当前用户的权限,也可以在页面更改该用户的权限,更新之后保存.像下面这样. 填写用户名提交: 显示用户AAA的权限: 修改权限(增加article3): 点 ...

随机推荐

  1. ListView下拉刷新

    本内容为复制代码: 一.自定义ListView控件: package com.xczl.smart.view; import java.util.Date; import com.suliang.R; ...

  2. &num;8&period;10&period;16总结&num; 属性选择符 伪对象选择符 CSS的常用样式

    属性选择符 E[att] E[att="val"] E[att~="val"] E[att^="val"] E[att$="val ...

  3. for 循环

    #include <iostream> int main(){ ; ; val <= ; ++val) { sum += val; } std::cout << &quo ...

  4. ylbtech-Recode&lpar;记录&rpar;-数据库设计

    ylbtech-dbs:ylbtech-Recode(记录)-数据库设计 -- =============================================-- DatabaseName ...

  5. UVA 11478 Halum(用bellman-ford解差分约束)

    对于一个有向带权图,进行一种操作(v,d),对以点v为终点的边的权值-d,对以点v为起点的边的权值+d.现在给出一个有向带权图,为能否经过一系列的(v,d)操作使图上的每一条边的权值为正,若能,求最小 ...

  6. linux&lowbar;环境变量设置 utf-8

    echo $LANG 显示编码  :  en_US.UTF-8  英文urf8有时显示程序输出是? 解决方法: vim ~/.bashrc 最后一行追加: export LANG=zh_CN.UTF- ...

  7. document对象相关的几个常用的方法

    方法  描述 var newP=createElement("p"); 创建了一个p标签,p也可替换为div,span等 var oldTxt=createTextNode(&qu ...

  8. 基于本地文件系统的LocalDB

    零.前言 之前写一些小工具的时候,需要用到数据存储方面的技术,但是用数据库又觉得太大了,本地文件存储txt文件存储又不是很规范,于是乎想到了去编写一个简单的基于本地文件系统的数据存储库,暂且叫它loc ...

  9. 设置弹窗、遮罩的样式设置(包括:left、heigh等)

    .zhezhao { width:100%; background-color:#000; filter:alpha(opacity=50); -moz-opacity:0.5; opacity:0. ...

  10. Docker Swarm 环境搭建

    Docker Swarm 环境搭建 swarm 使用前提 1.Docker版本1.12+ Docker升级教程:https://www.cnblogs.com/xiangsikai/p/9935894 ...