1.shiro介绍
1.1介绍
shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证、用户授权。
shiro不依赖于spring,shiro不仅可以实现 web应用的权限管理,还可以实现c/s系统,分布式系统权限管理,shiro属于轻量框架,越来越多企业项目开始使用shiro。
使用shiro实现系统的权限管理,有效提高开发效率,从而降低开发成本。
1.2shrio功能特点:
1) Authentication:身份认证/登录,验证用户是不是拥有相应的身份。
2) Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限。
3) Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的。
4) Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储。
5) Web Support:Web支持,可以非常容易的集成到 web 环境。
6) Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率。
7) Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去。
8) Testing:提供测试支持。
9) Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。
10) Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
1.3shiro运行原理
1) Subject:主体,可以看到主体可以是任何与应用交互的“用户”。
2) SecurityManager:相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher。它是 Shiro 的核心,所有具体的交互都通过 SecurityManager 进行控制。它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。
3) Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,我们可以自定义实现。其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了。
4) Authrizer:授权器,或者访问控制器。它用来决定主体是否有权限进行相应的操作,即控制着用户能访问应用中的哪些功能。
5) Realm:可以有1个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的。它可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等。
6) SessionManager:如果写过 Servlet 就应该知道 Session 的概念,Session 需要有人去管理它的生命周期,这个组件就是 SessionManager。而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境。
7) SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD。我们可以自定义 SessionDAO 的实现,控制 session 存储的位置。如通过 JDBC 写到数据库或通过 jedis 写入 redis 中。另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能。
8) CacheManager:缓存管理器。它来管理如用户、角色、权限等的缓存的。因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能。
9) Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密的。
1.4过滤器
当 Shiro 被运用到 web 项目时,Shiro 会自动创建一些默认的过滤器对客户端请求进行过滤。以下是 Shiro 提供的过滤器:
过滤器简称 |
对应的 Java 类 |
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 |
noSessionCreation |
org.apache.shiro.web.filter.session.NoSessionCreationFilter |
解释:
/admins/**=anon # 表示该 uri 可以匿名访问
/admins/**=auth # 表示该 uri 需要认证才能访问
/admins/**=authcBasic # 表示该 uri 需要 httpBasic 认证
/admins/**=perms[user:add:*] # 表示该 uri 需要认证用户拥有 user:add:* 权限才能访问
/admins/**=port[8081] # 表示该 uri 需要使用 8081 端口
/admins/**=rest[user] # 相当于 /admins/**=perms[user:method],其中,method 表示 get、post、delete 等
/admins/**=roles[admin] # 表示该 uri 需要认证用户拥有 admin 角色才能访问
/admins/**=ssl # 表示该 uri 需要使用 https 协议
/admins/**=user # 表示该 uri 需要认证或通过记住我认证才能访问
/logout=logout # 表示注销,可以当作固定配置
注意:
anon,authcBasic,auchc,user 是认证过滤器。
perms,roles,ssl,rest,port 是授权过滤器。
2.spring+springmvc+mybatis+shrio权限认证
2.1 pom.xml
<!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</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-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-quartz</artifactId> <version>1.2.3</version> </dependency> |
2.2 web.xml配置shiro
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath*:applicationContext.xml classpath*:applicationContext-shiro.xml </param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 用户单会话过滤器 --> <!-- <filter> <filter-name>userSingleSessionFilter</filter-name> <filter-class>com.vulnverify.web.filter.UserSingleSessionFilter</filter-class> </filter> <filter-mapping> <filter-name>userSingleSessionFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>-->
<!-- 字符编码过滤器 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <!-- shiro权限 --> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <async-supported>true</async-supported> <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> <!-- log4j日志 --> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>classpath:log4j.properties</param-value> </context-param> <context-param> <param-name>log4jRefreshInterval</param-name> <param-value>60000</param-value> </context-param> <listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener> <!-- springmvc DispatcherServlet --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>/index.jsp</welcome-file> </welcome-file-list> <error-page> <error-code>404</error-code> <location>/page/404</location> </error-page> <error-page> <error-code>500</error-code> <location>/page/500</location> </error-page> <error-page> <exception-type>org.apache.shiro.authz.AuthorizationException</exception-type> <location>/page/401</location> </error-page> <error-page> <error-code>400</error-code> <location>/page/400</location> </error-page> </web-app> |
2.3 shrio配置文件 applicationContext-shiro.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans classpath:schema/spring-beans.xsd http://www.springframework.org/schema/util classpath:schema/spring-util.xsd">
<description>apache shiro配置</description>
<!-- web.xml中shiro的filter对应的bean --> <!-- Shiro 的Web过滤器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证 --> <property name="loginUrl" value="/static/login/loginPanel/login.html"/> <!-- 认证成功跳转界面 --> <property name="successUrl" value="/static/index.html"/> <!-- 通过unauthorizedUrl指定没有权限操作时跳转页面 --> <property name="unauthorizedUrl" value="/page/401"/> <property name="filterChainDefinitions"> <value> <!-- 静态资源允许访问 --> /static/** = anon <!-- 登录页允许访问 --> /user/login = anon /publicKey = anon /page/exception = anon /verificationCode = anon <!-- 其他资源需要认证 --> /** = authc </value> </property> </bean>
<!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- 注入realm --> <property name="realms"> <list> <ref bean="securityRealm"/> </list> </property> <!-- 注入缓存管理器 --> <property name="cacheManager" ref="shiroEhcacheManager" /> <!-- 注入sessiong管理器 --> <property name="sessionManager" ref="sessionManager" /> <!-- 记住我 --> <property name="rememberMeManager" ref="rememberMeManager" /> </bean>
<!-- 缓存管理器 使用Ehcache实现 --> <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/> </bean>
<!-- 会话DAO --> <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.MemorySessionDAO"/> <!-- <bean id="sessionDAO" class="com.vulnverify.web.session.SessionRedisDao"/> -->
<!-- 会话管理器 --> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <!-- <bean id="sessionManager" class="com.vulnverify.web.security.WebSessionManager"> --> <!-- session的失效时长,单位毫秒 --> <property name="globalSessionTimeout" value="600000" /> <!-- 删除失效的session --> <property name="deleteInvalidSessions" value="true" /> </bean>
<!-- sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID --> <bean id="sharesession" class="org.apache.shiro.web.servlet.SimpleCookie"> <!-- cookie的name,对应的默认是 JSESSIONID --> <constructor-arg name="name" value="SHAREJSESSIONID" /> <!-- jsessionId的path为 / 用于多个系统共享jsessionId --> <property name="path" value="/" /> <property name="httpOnly" value="true"/> </bean> <!-- rememberMeManager管理器,写cookie,取出cookie生成用户信息 --> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <property name="cookie" ref="rememberMeCookie" /> </bean> <!-- 记住我cookie --> <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <!-- rememberMe是cookie的名字 --> <constructor-arg value="rememberMe" /> <!-- 记住我cookie生效时间30天 --> <property name="maxAge" value="2592000" /> </bean>
<!-- 自定义 realm 安全数据 --> <bean id="securityRealm" class="com.vulnverify.web.security.SecurityRealm"></bean>
<!-- Shiro生命周期处理器 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
</beans> |
2.4 自定义realm 安全数据库
package com.vulnverify.web.security; import java.util.List; import javax.annotation.Resource; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.stereotype.Component; import com.vulnverify.web.model.TSysRights; import com.vulnverify.web.model.TSysRole; import com.vulnverify.web.model.TUser; import com.vulnverify.web.service.SysRightsService; import com.vulnverify.web.service.SysRoleService; import com.vulnverify.web.service.UserService; /** * 用户身份验证,授权 Realm 组件 * * @author linan **/ @Component(value = "securityRealm") public class SecurityRealm extends AuthorizingRealm { @Resource private UserService sysUserService;
@Resource private SysRoleService sysRoleService;
@Resource private SysRightsService sysRightsService;
/** * 权限检查 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); final TUser user = (TUser)SecurityUtils.getSubject().getSession(false).getAttribute("userInfo"); String roleType = user.getUserType();//用户角色 //角色信息 final TSysRole roleInfos = sysRoleService.selectRoleByRoleType(Integer.parseInt(roleType)); if(null!=roleInfos){ // 添加角色 authorizationInfo.addRole(roleInfos.getRoleName()); //根据角色id查询角色权限 final List<TSysRights> sysRightsList = sysRightsService.selectSysRightsByRoleId(roleInfos.getRoleId()); for (TSysRights sysRights : sysRightsList) { // 添加权限 authorizationInfo.addStringPermission(sysRights.getRightCode()); } } return authorizationInfo; } /** * 身份验证信息 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //身份 String loginName = String.valueOf(token.getPrincipal()); //密码 String password = new String((char[]) token.getCredentials()); TUser su = new TUser(); su.setUserAccount(loginName); su.setPassword(password); // 通过数据库进行验证 final TUser authentication = sysUserService.authentication(su); if (authentication == null) { throw new AuthenticationException("用户名或密码错误."); } //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(loginName, password, getName()); return authenticationInfo; } } |
2.5 登陆controller
package com.vulnverify.web.controller; import java.util.Date; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.validation.Valid; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import com.vulnverify.core.entity.PageData; import com.vulnverify.core.entity.PageQuery; import com.vulnverify.core.entity.SimpleException; import com.vulnverify.core.orm.mybatis.Page; import com.vulnverify.core.redis.RedisDb; import com.vulnverify.core.utils.ApplicationUtils; import com.vulnverify.core.utils.DateUtil; import com.vulnverify.web.constant.Constant; import com.vulnverify.web.model.TUser; import com.vulnverify.web.model.requestbody.IdReqBody; import com.vulnverify.web.model.requestbody.LoginReqBody; import com.vulnverify.web.model.requestbody.UserCreateReqBody; import com.vulnverify.web.model.requestbody.UserListReqBody; import com.vulnverify.web.model.requestbody.UserModifyReqBody; import com.vulnverify.web.model.responsebody.LoginResBody; import com.vulnverify.web.model.view.UserView; import com.vulnverify.web.service.UserService; /** * 用户控制类 * @author linan * @date 2018年4月23日 * */ @Controller @RequestMapping(value="/user") public class UserController extends BaseController{ private static Logger logger=LoggerFactory.getLogger(UserController.class); @Resource private UserService userService; /**验证码验证是否开启的标识*/ @Value("${verfication.code.check}") private String verficationCodeCheck = "true"; /** * 登陆 * @param loginBody * @param request * @param response * @return * @throws Exception */ @RequestMapping(value = "/login", method = RequestMethod.POST) public Object login(@Valid @RequestBody LoginReqBody loginBody,HttpServletRequest request,HttpServletResponse response) throws Exception{ Subject subject = SecurityUtils.getSubject(); Session session = subject.getSession(false); try{
//验证码 /*String verificationCode = (String)session.getAttribute("verificationCode"); if("true".equals(verficationCodeCheck)){ if(verificationCode == null || !verificationCode.equalsIgnoreCase(loginBody.getVerificationCode())){ throw new SimpleException(Constant.EXCEPTION_S0010005, ApplicationUtils.getMessage(Constant.EXCEPTION_S0010005)); } }*/
if (subject.isAuthenticated()) { throw new SimpleException(Constant.EXCEPTION_S0010006, ApplicationUtils.getMessage(Constant.EXCEPTION_S0010006)); }
final TUser authUserInfo = userService.getUserByUserAccout(loginBody.getUserAccount()); if(authUserInfo != null){ if(authUserInfo.getStatus() == Constant.USER_STATE_UNABLE){ throw new Exception("用户"+authUserInfo.getUserAccount()+"已被停用"); } } String sha256Hex = ApplicationUtils.sha256Hex(loginBody.getPassword()); String password = authUserInfo.getPassword(); if(sha256Hex.equals(password)){ System.out.println("------true---------"); } //调用shrio的自定义realm的doGetAuthenticationInfo验证身份 subject.login( new UsernamePasswordToken( loginBody.getUserAccount(), ApplicationUtils.sha256Hex(loginBody.getPassword())));
session.setAttribute("userInfo", authUserInfo); session.setAttribute("userKey",loginBody.getUserKey()); logger.info("login userKey is "+session.getId()+":"+loginBody.getUserKey());
/* String key = "loginUser."+authUserInfo.getUserAccount(); RedisDb.setString(key, session.getId().toString()); RedisDb.expireString(key, 1800);*/
LoginResBody lrb = new LoginResBody(); lrb.setId(authUserInfo.getUserId()+""); lrb.setUserAccount(authUserInfo.getUserAccount()); lrb.setUserName(authUserInfo.getUserName());
return generateResultData(lrb); }catch(Exception e){ TUser sysUser = userService.getUserByUserAccout(loginBody.getUserAccount()); if(sysUser != null){ session.setAttribute("failUserInfo", sysUser); // ApplicationUtils.optData2Request(sysUser.getUserName()); } throw e; }finally{ session.removeAttribute("verificationCode"); } } } |
2.6权限验证:
2.6.1 shiro什么时候会进入doGetAuthorizationInfo(PrincipalCollection principals)
会进入授权方法一共有三种情况!
1、subject.hasRole(“admin”) 或 subject.isPermitted(“admin”):自己去调用这个是否有什么角色或者是否有什么权限的时候;
2、在方法上加注解的时候
@RequiresRoles("admin") :角色验证
@RequiresPermissions(value = PermissionSign.GET_ORGIP_LIST)权限验证
PermissionSign为常量类。与realm中授权的添加权限对应。
3、[@shiro.hasPermission name = "admin"][/@shiro.hasPermission]:在页面上加shiro标签的时候,即进这个页面的时候扫描到有这个标签的时候。
2.7运行流程:
1.登陆调用userController登陆中的 subject.login(new UsernamePasswordToken( loginBody.getUserAccount(), ApplicationUtils.sha256Hex(loginBody.getPassword())));方法
2.调用自定义realm中的doGetAuthenticationInfo(AuthenticationToken token)进行身份登陆验证。
3.方法中
@RequiresPermissions()权限会去自定义realm的授权接口doGetAuthorizationInfo(PrincipalCollection principals) 去授权,然后判断是否有操作此方法的权限。
3.参考文档:
https://www.cnblogs.com/moonlightL/p/8126910.html
https://blog.csdn.net/mine_song/article/details/61616259
http://jinnianshilongnian.iteye.com/blog/2022468