Apache Shiro Features 特性
Authentication(认证), Authorization(授权), Session Management(会话管理), Cryptography(加密)被 Shiro 框架的开发团队称之为应用安全的四大基石。那么就让我们来看看它们吧:
Authentication(认证):用户身份识别,通常被称为用户“登录”
Authorization(授权):访问控制。比如某个用户是否具有某个操作的使用权限。
Session Management(会话管理):特定于用户的会话管理,甚至在非web 或 EJB 应用程序。
Cryptography(加密):在对数据源使用加密算法加密的同时,保证易于使用。
High-Level Overview 高级概述
在概念层,Shiro 架构包含三个主要的理念:Subject,SecurityManager和 Realm。
Subject:当前用户,Subject 可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它–当前和软件交互的任何事件。
SecurityManager:管理所有Subject,SecurityManager 是 Shiro 架构的核心,配合内部安全组件共同组成安全伞。
Realms:用于进行权限信息的验证,我们自己实现。Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)。
我们需要实现Realms的Authentication 和 Authorization。其中 Authentication 是用来验证用户身份,Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
pom包依赖
pom.xml
<shiro.version>1.3.2</shiro.version>
<dependency>
<groupId>com.zteict.baseplatform</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>${zteict-stater.version}</version>
</dependency>
Shiro 配置
首先要配置的是ShiroConfig类,Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。 既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。
MyShiroConfig.java
package com.zteict;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
@Configuration
public class MyShiroConfig {
@Bean
public ShiroFilterFactoryBean shirFilter(org.apache.shiro.mgt.SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面;
//shiroFilterFactoryBean.setUnauthorizedUrl("/test/unauthorized");
// 拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/ajaxlogin", "anon");
// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
//filterChainDefinitionMap.put("/add", "perms[权限添加]");
// <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public MyPrincipalAuthorizingRealm myShiroRealm(){
return new MyPrincipalAuthorizingRealm();
}
@Bean
public org.apache.shiro.mgt.SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
@Bean
public HandlerExceptionResolver solver(){
HandlerExceptionResolver handlerExceptionResolver=new MyExceptionResolver();
return handlerExceptionResolver;
}
}
Filter Chain定义说明:
1、一个URL可以配置多个Filter,使用逗号分隔
2、当设置多个过滤器时,全部验证通过,才视为通过
3、部分过滤器可指定参数,如perms,roles
Shiro内置的FilterChain
anon:所有url都都可以匿名访问
authc: 需要认证才能进行访问
user:配置记住我或认证通过可以访问
登录认证实现
在认证、授权内部实现机制中都有提到,最终处理都将交给Real进行处理。因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO. Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。
该方法主要执行以下操作:
1、检查提交的进行认证的令牌信息
2、根据令牌信息从数据源(通常为数据库)中获取用户信息
3、对用户信息进行匹配验证。
4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
5、验证失败则抛出AuthenticationException异常信息。
而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo(),重写获取用户信息的方法。
doGetAuthenticationInfo的重写
MyPrincipalAuthorizingRealm.java
package com.zteict;
import java.util.List;
import javax.annotation.Resource;
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.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.stereotype.Component;
import com.zteict.dao.AppPermissionMapper;
import com.zteict.dao.AppUserMapper;
import com.zteict.entity.AppRole;
import com.zteict.entity.AppUser;
@Component
public class MyPrincipalAuthorizingRealm extends AuthorizingRealm {
@Resource
AppUserMapper appUserMapper;
@Resource
AppPermissionMapper appPermissionMapper;
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken usertoken=(UsernamePasswordToken) token;
String userName = usertoken.getUsername();
// String userName = (String) token.getPrincipal();
AppUser user = appUserMapper.getAppUser(userName);
if (user == null) {
throw new UnauthorizedException("用户未登陆");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPwd(),this.getName());
return info;
}
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
AppUser user = (AppUser) principals.getPrimaryPrincipal();
if(user!=null){
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//角色
List<AppRole> roleList;
roleList = appPermissionMapper.getRolesByUser(user.getUserCode());
for (AppRole role:roleList) {
info.addRole(role.getRoleName());
}
//权限
/*List<Permission> permissions = permissionService.getPermissionByRoleId(user.getUserId());
for (Permission p : permissions){
info.addStringPermission(String.valueOf(p.getPermissionId()));
} */
return info;
}
return null;
}
}
链接权限的实现
shiro的权限授权是通过继承AuthorizingRealm抽象类,重载doGetAuthorizationInfo();当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行,所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。在这个方法中主要是使用类:SimpleAuthorizationInfo进行角色的添加和权限的添加。
登录实现
@PostMapping("/ajaxlogin")
@ResponseBody
public Message login(@RequestBody AppUser user) throws IOException {
Message msg = new Message();
Subject currentUser = SecurityUtils.getSubject();
if ( currentUser.isAuthenticated() )
currentUser.logout();
if ( !currentUser.isAuthenticated() ) {
UsernamePasswordToken token = new UsernamePasswordToken(user.getUserCode(), user.getPwd());
//token.setRememberMe(true);
try {
currentUser.login(token);
} catch(Exception e) {
msg.setFlag(false);
msg.setDesc(MessageTip.NO_USER);
return msg;
}
}
msg.setFlag(true);
return msg;
}
授权实现
@RequiresRoles("admin")
@RequestMapping("/openAuthor")
@ResponseBody
public ModelAndView openAuthor(@RequestParam(required=true,defaultValue = "0") String roleId) throws IOException {
ModelAndView m = new ModelAndView ();
m.setViewName("common/open/openAuthorWindow");
m.addObject("roleId", roleId);
return m;
}
报错页面处理
MyExceptionResolver.java
package com.zteict;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
public class MyExceptionResolver implements HandlerExceptionResolver{
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
if(ex instanceof UnauthorizedException){
ModelAndView mv = new ModelAndView("error/403");
return mv;
}
ModelAndView mv = new ModelAndView("error/500");
mv.addObject("exception", ex.toString().replaceAll("\n", "<br/>"));
return mv;
}
}