spring整合应用安全框架Shiro
2017-07-17 21:46
656 查看
Shiro的介绍
Apache Shiro是一个强大易用的Java安全框架,它提供的主要功能有:认证 -——用户身份识别,常被称为用户“登录”;
授权—— 访问控制;
密码加密——保护或隐藏数据防止被偷窥;
会话管理——每用户相关的时间敏感的状态。
Shiro的三个核心组件(Subject,SecurityManager 和 Realms)介绍
Subject:“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。 Subject代表的是当前用户的安全操作。SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。(当配置Shiro时,你至少要指定一个Realm,用于认证和(或)授权,至少需要一个。 Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。)
下图为Shiro功能模块结构:
这些模块各有作用:
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web支持,可以非常容易的集成到Web环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
Shiro依赖包
maven环境下,pom.xml中依赖包配置:<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</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-ehcache</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.3</version> </dependency>
web工程中引入Shiro框架,首先要在web.xml中配置:
<!-- Apache Shiro --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:application.xml,classpath:shiro/spring-shiro.xml</param-value> </context-param> <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>
web程序启动时,首先会加载spring-shiro.xml配置文件,然后执行web中的过滤器,实现安全登录。
配置Realm,进行验证及授权
定义该一个安全认证的实现类,需要继承AuthorizingRealm并实现登录验证和赋予角色权限的两个方法
即:
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationTokenauthcToken);--------登录认证时使用
protected AuthorizationInfogetAuthorizationInfo(PrincipalCollection principals);---------用户授权时使用
还可以自定义一些其他业务中使用到的方法,如下:
@SuppressWarnings("restriction") @Service //@DependsOn({"userDao","roleDao","menuDao"}) public class SystemAuthorizingRealm extends AuthorizingRealm { private Logger logger = LoggerFactory.getLogger(getClass()); private SystemService systemService; public SystemAuthorizingRealm() { this.setCachingEnabled(false); } /** * 认证回调函数, 登录时调用 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) { UsernamePasswordToken token = (UsernamePasswordToken) authcToken; int activeSessionSize = getSystemService().getSessionDao().getActiveSessions(false).size(); if (logger.isDebugEnabled()){ logger.debug("login submit, active session size: {}, username: {}", activeSessionSize, token.getUsername()); } // 校验登录验证码 if (LoginController.isValidateCodeLogin(token.getUsername(), false, false)){ Session session = UserUtils.getSession(); String code = (String)session.getAttribute(ValidateCodeServlet.VALIDATE_CODE); if (token.getCaptcha() == null || !token.getCaptcha().toUpperCase().equals(code)){ throw new AuthenticationException("msg:验证码错误, 请重试."); } } // 校验用户名密码 User user = getSystemService().getUserByLoginName(token.getUsername()); if (user != null) { if (Global.NO.equals(user.getLoginFlag())){ throw new AuthenticationException("msg:该已帐号禁止登录."); } byte[] salt = Encodes.decodeHex(user.getPassword().substring(0,16)); return new SimpleAuthenticationInfo(new Principal(user, token.isMobileLogin()), user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName()); } else { return null; } } /** * 获取权限授权信息,如果缓存中存在,则直接从缓存中获取,否则就重新获取, 登录成功后调用 */ protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) { if (principals == null) { return null; } AuthorizationInfo info = null; info = (AuthorizationInfo)UserUtils.getCache(UserUtils.CACHE_AUTH_INFO); if (info == null) { info = doGetAuthorizationInfo(principals); if (info != null) { UserUtils.putCache(UserUtils.CACHE_AUTH_INFO, info); } } return info; } /** * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { Principal principal = (Principal) getAvailablePrincipal(principals); // 获取当前已登录的用户 if (!Global.TRUE.equals(Global.getConfig("user.multiAccountLogin"))){ Collection<Session> sessions = getSystemService().getSessionDao().getActiveSessions(true, principal, UserUtils.getSession()); if (sessions.size() > 0){ // 如果是登录进来的,则踢出已在线用户 if (UserUtils.getSubject().isAuthenticated()){ for (Session session : sessions){ getSystemService().getSessionDao().delete(session); } } // 记住我进来的,并且当前用户已登录,则退出当前用户提示信息。 else{ UserUtils.getSubject().logout(); throw new AuthenticationException("msg:账号已在其它地方登录,请重新登录。"); } } } User user = getSystemService().getUserByLoginName(principal.getLoginName()); if (user != null) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); List<Menu> list = UserUtils.getMenuList(); for (Menu menu : list){ if (StringUtils.isNotBlank(menu.getPermission())){ // 添加基于Permission的权限信息 for (String permission : StringUtils.split(menu.getPermission(),",")){ info.addStringPermission(permission); } } } // 添加用户权限 info.addStringPermission("user"); // 添加用户角色信息 for (Role role : user.getRoleList()){ info.addRole(role.getEnname()); } // 更新登录IP和时间 getSystemService().updateUserLoginInfo(user); // 记录登录日志 LogUtils.saveLog(Servlets.getRequest(), "系统登录"); return info; } else { return null; } } @Override protected void checkPermission(Permission permission, AuthorizationInfo info) { authorizationValidate(permission); super.checkPermission(permission, info); } @Override protected boolean[] isPermitted(List<Permission> permissions, AuthorizationInfo info) { if (permissions != null && !permissions.isEmpty()) { for (Permission permission : permissions) { authorizationValidate(permission); } } return super.isPermitted(permissions, info); } @Override public boolean isPermitted(PrincipalCollection principals, Permission permission) { authorizationValidate(permission); return super.isPermitted(principals, permission); } @Override protected boolean isPermittedAll(Collection<Permission> permissions, AuthorizationInfo info) { if (permissions != null && !permissions.isEmpty()) { for (Permission permission : permissions) { authorizationValidate(permission); } } return super.isPermittedAll(permissions, info); } /** * 授权验证方法 * @param permission */ private void authorizationValidate(Permission permission){ // 模块授权预留接口 } /** * 设定密码校验的Hash算法与迭代次数 */ @PostConstruct public void initCredentialsMatcher() { HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(SystemService.HASH_ALGORITHM); matcher.setHashIterations(SystemService.HASH_INTERATIONS); setCredentialsMatcher(matcher); } /** * 获取系统业务对象 */ public SystemService getSystemService() { if (systemService == null){ systemService = SpringContextHolder.getBean(SystemService.class); } return systemService; } /** * 授权用户信息 */ public static class Principal implements Serializable { private static final long serialVersionUID = 1L; private String id; // 编号 private String loginName; // 登录名 private String name; // 姓名 private boolean mobileLogin; // 是否手机登录 // private Map<String, Object> cacheMap; public Principal(User user, boolean mobileLogin) { this.id = user.getId(); this.loginName = user.getLoginName(); this.name = user.getName(); this.mobileLogin = mobileLogin; } public String getId() { return id; } public String getLoginName() { return loginName; } public String getName() { return name; } public boolean isMobileLogin() { return mobileLogin; } /** * 获取SESSIONID */ public String getSessionid() { try{ return (String) UserUtils.getSession().getId(); }catch (Exception e) { return ""; } } @Override public String toString() { return id; } } }
Shiro配置文件
<?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.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd" default-lazy-init="true"> <description>Shiro Configuration</description> <!-- 加载配置属性文件 --> <context:property-placeholder ignore-unresolvable="true" location="classpath:jeesite.properties" /> <!-- Shiro权限过滤过滤器定义 --> <bean name="shiroFilterChainDefinitions" class="java.lang.String"> <constructor-arg> <value> /static/** = anon /userfiles/** = anon ${adminPath}/cas = cas ${adminPath}/login = authc ${adminPath}/logout = logout ${adminPath}/** = user /act/editor/** = user /ReportServer/** = 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> <!-- CAS认证过滤器 --> <bean id="casFilter" class="org.apache.shiro.cas.CasFilter"> <property name="failureUrl" value="${adminPath}/login"/> </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.thinkgem.jeesite.common.security.shiro.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="jeesite.session.id"/> </bean> <!-- 自定义Session存储容器 --> <bean id="sessionDAO" class="com.thinkgem.jeesite.common.security.shiro.session.CacheSessionDAO"> <property name="sessionIdGenerator" ref="idGen" /> <property name="activeSessionsCacheName" value="activeSessionsCache" /> <property name="cacheManager" ref="shiroCacheManager" /> </bean> <!-- 自定义系统缓存管理器--> <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>
这里做一下说明,Shiro默认到的权限验证类别:
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没有参数表示必须存在用户,当登入操作时不做检查
注意:
Shiro.xml加载配置是从上而下的,也就是向上面的配置,如/** = anon ,如果把这个配置在第一行,那么下面的配置都没用。因为是从上往下去匹配,只要匹配中了,就不匹配了所以必须要有序。
相关文章推荐
- [置顶] OSGI企业应用开发(十)整合Spring和Mybatis框架(三)
- spring整合shiro框架的实现步骤记录
- Portal-Basic Java Web 应用开发框架:应用篇(十一) —— 整合 Spring
- Apache Shiro权限框架在SpringMVC+Hibernate中的应用
- SpringMVC整合Shiro安全框架(一)
- OSGI企业应用开发(八)整合Spring和Mybatis框架(一)
- Spring Boot 构建应用——整合 Dubbo 框架
- 将 Shiro 作为应用的权限基础 五:SpringMVC+Apache Shiro+JPA(hibernate)整合配置
- j2ee 设计框架 ibatis+spring+struts整合应用实例
- Spring Boot系列(十五) 安全框架Apache Shiro(二)缓存-EhCache
- 安全框架 - Shiro与springMVC整合的注解以及JSP标签
- OSGI企业应用开发(九)整合Spring和Mybatis框架(二)
- 与spring整合shiro框架
- shiro与spring整合在web项目中的应用
- Spring与其他工具、框架整合应用
- springboot整合shiro应用
- 【SSM框架 SSM项目源码 SSM源码 下载】java框架整合Springmvc+mybatis+shiro+bootstrap
- spring整合Ehcache框架并且完成与shiro框架的整合
- spring boot配置shiro安全框架及用户登录权限验证实现
- SpringBoot整合shiro框架