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功能模块结构:
![](https://img-blog.csdn.net/20170717214655179)
这些模块各有作用:
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中配置:
-
-
- <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
-
- 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())){
-
- for (String permission : StringUtils.split(menu.getPermission(),",")){
- info.addStringPermission(permission);
- }
- }
- }
-
- info.addStringPermission("user");
-
- for (Role role : user.getRoleList()){
- info.addRole(role.getEnname());
- }
-
- 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);
- }
-
-
-
-
-
- private void authorizationValidate(Permission permission){
-
- }
-
-
-
-
- @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;
-
-
-
- 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;
- }
-
-
-
-
- public String getSessionid() {
- try{
- return (String) UserUtils.getSession().getId();
- }catch (Exception e) {
- return "";
- }
- }
-
- @Override
- public String toString() {
- return id;
- }
-
- }
- }
Shiro配置文件
这里做一下说明,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 ,如果把这个配置在第一行,那么下面的配置都没用。因为是从上往下去匹配,只要匹配中了,就不匹配了所以必须要有序。