Shiro是Apache旗下的一款产品,是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。Shiro在日常工作开发中并不少见,因为相较于Spring Security成熟但是复杂的开发体系而言,Shiro的上手只需要几天,而且在授权和验证的配置上比较简单。
Shiro整体架构
Shiro的整体架构如下图:
Subject:即“当前操作用户”。泛指当前操作的事务,不仅仅局限于“人”这一概念,可以使后台进程,账户等类似的,不过管理系统中,由于Shiro主要是做用户的授权和验证,所以可以粗略的人为Subjec为当前用户
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
Shiro主要功能:
- Authentication(认证):用户身份识别,通常被称为用户“登录”
- Authorization(授权):访问控制。比如某个用户是否具有某个操作的使用权限。
- Session Management(会话管理):特定于用户的会话管理,甚至在非web 或 EJB 应用程序。
- Cryptography(加密):在对数据源使用加密算法加密的同时,保证易于使用。
介绍到此,开始进行项目实践
引入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
表结构设计
总共五张表:用户、角色、权限表以及用户角色关联表,角色权限关联表,如下图:
shiro配置
继承AuthorizingRealm这个类后,会要求重写两个方法,一个是授权,一个是验证。
@Configuration
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 授权
*
* @param principalCollection
* @return authorizationInfo
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
User user = (User) principalCollection.getPrimaryPrincipal();
for (Role role : user.getRoleList()) {
authorizationInfo.addRole(role.getRoleName());
for (Permission permission : role.getPermissionList()) {
authorizationInfo.addStringPermission(permission.getPermission());
}
}
return authorizationInfo;
}
/**
* 验证
*
* @param authenticationToken
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username = (String) authenticationToken.getPrincipal();
//Shiro有时间间隔机制,2分钟内不会重复执行该方法
User user = userService.getUserByUserName(username);
if (user != null) {
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUserName(), user.getUserPasswrod(), getName());
return authenticationInfo;
}
return null;
}
}
然后是Shiro的配置 ,注入完realm和securityManager后,编写过滤器栈。
@Configuration
public class ShiroConfig {
@Bean
public MyRealm myRealm() {
MyRealm myRealm = new MyRealm();
//如果有加密类操作,在这里执行
return myRealm;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager(myRealm());
return manager;
}
/**
* 配置过滤器栈
*/
@Bean
public ShiroFilterFactoryBean filterFactoryBean() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
//使用LinkHashMap保证有序执行
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//anon:可匿名访问
//authc:需要认证
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/index");
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 开启权限注解支持
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean(name = "simpleMappingExceptionResolver")
public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
Properties mapping = new Properties();
mapping.setProperty("DatabaseExpection", "databaseError");
mapping.setProperty("UnauthorizedException", "403");
simpleMappingExceptionResolver.setExceptionMappings(mapping);
simpleMappingExceptionResolver.setDefaultErrorView("error");
simpleMappingExceptionResolver.setExceptionAttribute("ex");
return simpleMappingExceptionResolver;
}
}
controller层及测试
@Controller
public class LoginController {
@RequestMapping({"/", "/index"})
public String index() {
return "/index";
}
@RequestMapping("/login")
public String login(HttpServletRequest request, Map<String, Object> map) {
String exception = (String) request.getAttribute("shiroLoginFailure");
String msg = "";
if (exception != null) {
if (UnknownAccountException.class.getName().equals(exception)) {
msg = "账号不存在";
} else if (IncorrectCredentialsException.class.getName().equals(exception)) {
msg = "密码错误";
} else {
msg = "其他错误" + exception;
}
}
map.put("msg", msg);
return "/login";
}
@RequestMapping("/403")
public String unAuthorizedRole() {
return "403";
}
}
@Controller
@RequestMapping("user")
public class UserController {
/**
* 用户查询
*/
@RequestMapping("/userList")
@RequiresPermissions("user:view")
public String userInfo() {
return "userInfo";
}
/**
* 用户添加;
*/
@RequestMapping("/userAdd")
@RequiresPermissions("user:add")
public String userAdd() {
return "userAdd";
}
/**
* 用户删除;
*/
@RequestMapping("/userDel")
@RequiresPermissions("user:del")
public String userDel() {
return "userDel";
}
}
访问:http://localhost:8080/login 显示登录界面
进去后根据不同的路径返回不同的结果