Shiro是Apache旗下的一个开源项目,它是一个非常易用的安全框架,提供了包括认证、授权、加密、会话管理等功能,与Spring Security一样属基于权限的安全框架,但是与Spring Security 相比,Shiro使用了比较简单易懂易于使用的授权方式。Shiro属于轻量级框架,相对于Spring Security简单很多,并没有security那么复杂。
优势特点
它是一个功能强大、灵活的、优秀的、开源的安全框架。
它可以胜任身份验证、授权、企业会话管理和加密等工作。
它易于使用和理解,与Spring Security相比,入门门槛低。
主要功能
验证用户身份
用户访问权限控制
支持单点登录(SSO)功能
可以响应认证、访问控制,或Session事件
支持提供“Remember Me”服务
框架体系
Shiro 的整体框架大致如下图所示(图片来自互联网):
Authentication(认证), Authorization(授权), Session Management(会话管理), Cryptography(加密)代表Shiro应用安全的四大基石。
它们分别是:
Authentication(认证):用户身份识别,通常被称为用户“登录”。
Authorization(授权):访问控制。比如某个用户是否具有某个操作的使用权限。
Session Management(会话管理):特定于用户的会话管理,甚至在非web 应用程序。
Cryptography(加密):在对数据源使用加密算法加密的同时,保证易于使用。
除此之外,还有其他的功能来支持和加强这些不同应用环境下安全领域的关注点。
特别是对以下的功能支持:
Web支持:Shiro 提供的 web 支持 api ,可以很轻松的保护 web 应用程序的安全。
缓存:缓存是 Apache Shiro 保证安全操作快速、高效的重要手段。
并发:Apache Shiro 支持多线程应用程序的并发特性。
测试:支持单元测试和集成测试,确保代码和预想的一样安全。
“Run As”:这个功能允许用户在许可的前提下假设另一个用户的身份。
“Remember Me”:跨 session 记录用户的身份,只有在强制需要时才需要登录。
主要流程
在概念层,Shiro 架构包含三个主要的理念:Subject, SecurityManager 和 Realm。下面的图展示了这些组件如何相互作用,我们将在下面依次对其进行描述。
接下来,我们就通过一个具体的案例,来讲解如何进行Shiro的整合,然后借助Shiro实现登录认证和访问控制。
添加相关依赖
清理掉不需要的测试类及测试依赖,添加 Maven 相关依赖,这里需要添加上WEB、Swagger、JPA和Shiro的依赖,Swagger的添加是为了方便接口测试。
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.1.5.RELEASE
com.louis.springboot
demo
0.0.1-SNAPSHOT
demo
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-web
io.springfox
springfox-swagger2
2.9.2
io.springfox
springfox-swagger-ui
2.9.2
org.springframework.boot
spring-boot-starter-data-jpa
mysql
mysql-connector-java
org.apache.shiro
shiro-spring
1.4.1
org.springframework.boot
spring-boot-maven-plugin
src/main/java
**/sqlmap/*.xml
false
src/main/resources
**/*.*
true
编写业务代码
添加一个用户类User,包含用户名和密码,用来进行登录认证,另外用户可以拥有角色。
User.java
package com.louis.springboot.demo.model;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(unique = true)
private String name;
private String password;
@OneToMany(cascade = CascadeType.ALL,mappedBy = "user")
private List roles;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List getRoles() {
return roles;
}
public void setRoles(List roles) {
this.roles = roles;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
添加一个角色类Role,表示用户角色,角色拥有可操作的权限集合。
Role.java
package com.louis.springboot.demo.model;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
@Entity
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String roleName;
@ManyToOne(fetch = FetchType.EAGER)
private User user;
@OneToMany(cascade = CascadeType.ALL,mappedBy = "role")
private List permissions;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public List getPermissions() {
return permissions;
}
public void setPermissions(List permissions) {
this.permissions = permissions;
}
}
添加一个权限类Permission,表示资源访问权限。
Permission.java
package com.louis.springboot.demo.model;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
@Entity
public class Permission {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String permission;
@ManyToOne(fetch = FetchType.EAGER)
private Role role;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
添加一个登录控制器,编写相关的接口。create接口添加了@RequiresPermissions(“create”),用于进行权限注解测试。
LoginController.java
package com.louis.springboot.demo.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.louis.springboot.demo.model.Role;
import com.louis.springboot.demo.model.User;
import com.louis.springboot.demo.service.LoginService;
@RestController
public class LoginController {
@Autowired
private LoginService loginService;
/**
* POST登录
* @param map
* @return
*/
@PostMapping(value = "/login")
public String login(@RequestBody User user) {
// 添加用户认证信息
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(user.getName(), user.getPassword());
// 进行验证,这里可以捕获异常,然后返回对应信息
SecurityUtils.getSubject().login(usernamePasswordToken);
return "login ok!";
}
/**
* 添加用户
* @param user
* @return
*/
@PostMapping(value = "/addUser")
public String addUser(@RequestBody User user) {
user = loginService.addUser(user);
return "addUser is ok! \n" + user;
}
/**
* 添加角色
* @param role
* @return
*/
@PostMapping(value = "/addRole")
public String addRole(@RequestBody Role role) {
role = loginService.addRole(role);
return "addRole is ok! \n" + role;
}
/**
* 注解的使用
* @return
*/
@RequiresRoles("admin")
@RequiresPermissions("create")
@GetMapping(value = "/create")
public String create() {
return "Create success!";
}
@GetMapping(value = "/index")
public String index() {
return "index page!";
}
@GetMapping(value = "/error")
public String error() {
return "error page!";
}
/**
* 退出的时候是get请求,主要是用于退出
* @return
*/
@GetMapping(value = "/login")
public String login() {
return "login";
}
@GetMapping(value = "/logout")
public String logout() {
return "logout";
}
}
添加一个MyShiroRealm并继承AuthorizingRealm,实现其中的两个方法。
doGetAuthenticationInfo:实现用户认证,通过服务加载用户信息并构造认证对象返回。
doGetAuthorizationInfo:实现权限认证,通过服务加载用户角色和权限信息设置进去。
MyShiroRealm.java
package com.louis.springboot.demo.config;
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.beans.factory.annotation.Autowired;
import com.louis.springboot.demo.model.Permission;
import com.louis.springboot.demo.model.Role;
import com.louis.springboot.demo.model.User;
import com.louis.springboot.demo.service.LoginService;
/**
* 实现AuthorizingRealm接口用户用户认证
* @author Louis
* @date Jun 20, 2019
*/
public class MyShiroRealm extends AuthorizingRealm {
@Autowired
private LoginService loginService;
/**
* 用户认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
// 加这一步的目的是在Post请求的时候会先进认证,然后在到请求
if (authenticationToken.getPrincipal() == null) {
return null;
}
// 获取用户信息
String name = authenticationToken.getPrincipal().toString();
User user = loginService.findByName(name);
if (user == null) {
// 这里返回后会报出对应异常
return null;
} else {
// 这里验证authenticationToken和simpleAuthenticationInfo的信息
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name,
user.getPassword().toString(), getName());
return simpleAuthenticationInfo;
}
}
/**
* 角色权限和对应权限添加
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取登录用户名
String name = (String) principalCollection.getPrimaryPrincipal();
// 查询用户名称
User user = loginService.findByName(name);
// 添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for (Role role : user.getRoles()) {
// 添加角色
simpleAuthorizationInfo.addRole(role.getRoleName());
for (Permission permission : role.getPermissions()) {
// 添加权限
simpleAuthorizationInfo.addStringPermission(permission.getPermission());
}
}
return simpleAuthorizationInfo;
}
}
添加一个Shiro配置类,主要配置路由的访问控制,以及注入自定义的认证器MyShiroRealm。
ShiroConfig.java
package com.louis.springboot.demo.config;
import java.util.HashMap;
import java.util.Map;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
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;
@Configuration
public class ShiroConfig {
// 将自己的验证方式加入容器
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}
// 权限管理,配置主要是Realm的管理认证
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
// Filter工厂,设置对应的过滤条件和跳转条件
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map filterMap = new HashMap();
// 登出
filterMap.put("/logout", "logout");
// swagger
filterMap.put("/swagger**/**", "anon");
filterMap.put("/webjars/**", "anon");
filterMap.put("/v2/**", "anon");
// 对所有用户认证
filterMap.put("/**", "authc");
// 登录
shiroFilterFactoryBean.setLoginUrl("/login");
// 首页
shiroFilterFactoryBean.setSuccessUrl("/index");
// 错误页面,认证不通过跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
// 加入注解的使用,不加入这个注解不生效
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
参考资料
项目主页:http://shiro.apache.org/
W3C资料:https://www.w3cschool.cn/shiro/
百度百科:https://baike.baidu.com/item/shiro/17753571?fr=aladdin