这是一篇Shiro入门的博文,将整合Spring Boot快速入门Shiro。实现基本的认证、授权、密码加密功能
认证:也就是我们平时所说的登录,但认证不局限于账号密码登录,扫码、人脸识别、指纹等都可以算是认证
授权:不同的人拥有不同的权限,比如在后台管理系统中,管理员和普通用户看到的菜单是不一样的、有些资源普通用户只有读权限没有写权限等
Shiro简介
Shiro简单来说就是一个安全框架,它可以帮助我们快速、容易的实现认证、授权的相关功能。
我们先了解一下Shiro的架构。
在Shiro中有三个重要的角色
- Subject:主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;
- SecurityManager: 安全管理器,即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;
- Realm:Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。
Shiro整合Spring Boot快速入门
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.8.0</version>
</dependency>
<!-- Shiro没有提供登录页面,所以需要我们自己写 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2、自定义一个UserRealm。还记得Realm是干嘛的吗?
// 作用类比Spring Security中的UserDetailsService
public class UserRealm extends AuthorizingRealm {
// 模拟数据库中的数据
private Map<String, User> users = new HashMap<>();
{
// xiaowang拥有俩种权限,所有最终结果是他所有的资源都可以访问
users.put("xiaowang", new User("xiaowang", password, "user:add,user:update"));
users.put("xiaoming", new User("xiaoming", password, "user:update"));
}
// 类比Spring Security中的UserDetailsService的loadUserByUsername方法
// 根据username找到用户的信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证=>doGetAuthenticationInfo");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 模拟从数据库中根据用户名查用户信息
User user = users.get(token.getUsername());
if (user == null) {
throw new UnknownAccountException();
}
// user信息最终会被放在上下文中,在授权的时候就可以用到
return new SimpleAuthenticationInfo(user, user.getPassword(), "");
}
// 当访问的资源需要权限时,执行该方法。Spring Security的UserDetails包含认证信息和权限信息,所以只需要一个loadUserByUsername即可。而shiro的认证信息和权限信息是分开的,所以需要俩个方法
// 返回用户拥有的权限信息内容
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=>授权doGetAuthorizationInfo");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 获取当前登录用户的信息(在认证的时候保存的)
User user = (User) SecurityUtils.getSubject().getPrincipal();
// 告诉shiro当前用户有哪些权限
List<String> permissions = Arrays.stream(user.getPerms().split(",")).collect(Collectors.toList());
authorizationInfo.addStringPermissions(permissions);
return authorizationInfo;
}
}
3、配置安全管理器和资源访问规则
@Configuration
public class ShiroConfig {
// 创建realm对象
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
// 定义DefaultSecurityManager,并管理Realm
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
return securityManager;
}
// 定义资源访问规则
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultSecurityManager defaultSecurityManager) {
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
filterFactoryBean.setSecurityManager(defaultSecurityManager);
// 添加shiro内置的过滤器,配置资源访问规则
/**
* anon:无需认证就可以访问
* authc:必须认证了才能访问
* user:必须拥有记住我功能才能访问
* perms:对某个资源拥有权限才能访问
* role:拥有某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
// 告诉shiro资源的权限规则。注意顺序不要反了
filterMap.put("/r/r1","perms[user:add]"); // /r/r1资源需要拥有user:add权限
filterMap.put("/r/r2","perms[user:update]"); // /r/r2资源需要拥有user:update权限
filterMap.put("/r/**", "authc"); //所有/r开头的资源都需要认证后才能访问
filterFactoryBean.setFilterChainDefinitionMap(filterMap);
// 自定义未授权页面
filterFactoryBean.setUnauthorizedUrl("/unauthorized");
// 登录页面
filterFactoryBean.setLoginUrl("/toLogin");
return filterFactoryBean;
}
}
需要注意的它的匹配规则是从上到下的,是否允许访问,取决于第一条匹配的规则是否通过。如果将/r/**的规则写在上面,意味着只要登录了,就可以访问所有的资源。而我们配置的权限规则将会失效。所以我们一般将粒度大的规则写在后面。
4、定义登录接口、推出登录接口
@Controller
public class LoginController {
@RequestMapping("login")
public String login(String username, String password, Model model) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token); // 进行登录操作
return "loginSuccess";
} catch (UnknownAccountException uae) {
model.addAttribute("msg", "用户名不存在");
} catch (IncorrectCredentialsException ice) {
model.addAttribute("msg", "密码错误");
}
return "login";
}
@RequestMapping("logout")
public String logout() {
Subject subject = SecurityUtils.getSubject();
if(subject.isAuthenticated()) {
subject.logout();
}
System.out.println("退出登录成功");
return "login";
}
// 跳转到登录页面
@GetMapping("/toLogin")
public String toLogin() {
return "login";
}
}
这里简单介绍一下Shiro的身份认证流程:
- 首先调用 Subject.login(token) 进行登录,其会自动委托给 Security Manager
- SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;
- Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
- Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
- Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回 / 抛出异常表示身份验证成功了。此处可以配置多个 Realm,将按照相应的顺序及策略进行访问。
这段建议多看几遍,后面如果看源码,可以根据这个流程来看
至此Shiro的授权、认证流程已经完成了。剩下的就是一下测试用的代码了
5、定义资源文件
@RestController
public class ResourceController {
// 需要用户有user:add权限
@GetMapping("/r/r1")
public String r1() {
return "资源r1";
}
// 需要用户有user:update权限
@GetMapping("/r/r2")
public String r2() {
return "资源r2";
}
// 只需登录即可访问。
@GetMapping("/r/r3")
public String r3() {
return "资源r3";
}
// 允许匿名访问的资源
@GetMapping("/anony")
public String anony(){
return "匿名资源";
}
}
6. 在resource/templates下创建login.html页面
<form th:action="@{/login}">
<p>用户名<input type="text" name="username"></p>
<p>密码 <input type="text" name="password"></p>
<p><input type="submit"></p>
</form>
启动项目,验证下面几个测试用例
- 登录小王账号,所有的资源均可访问
- 登录小明账号,/r/r1资源没有权限访问,其他均可
- 未登录,只有匿名资源可以访问
简单总结一下几个关键的点:
- 配置Realm,Shiro需要借助Reaml获取用户的认证、授权等信息
- 配置安全管理器,管理Realm
- 通过ShiroFilterFactoryBean定义资源权限访问规则
- 通过Subject的login方法进行登录。logout方法进行推出登录
这只是一篇入门的文章,通过该文章,你可以快速的了解如何使用Shiro。此外为了避免篇幅部分非关键代码并没有贴出来,代码我已经放在码云仓库了。同时建议,如果你可以找一些使用了shiro的开源项目,看看在项目中如何使用shiro。
如果你想对shiro的机制更进一步的了解,你或许还需要去研究shiro的源码
个人碎叨叨
在搭建了Shiro和Spring Security的入门案例,个人觉得啊,Spring Security真的比Shiro好用多了。Spring Security帮我们做了大部分的东西,我们只需要少量的配置即可快速使用。
而且我感觉Spring Security API的设计也比Shiro好。比如资源访问权限规则的,Shiro是在Map中配置的,Spring Security的API就挺好的,还有密码加密,感觉Shiro的密码加密API设计不太好,Spring Security密码加密的API使用起来就很方便(这也是为什么Shiro的密码加密我要另外写一篇的原因,写在一起的话,代码的复杂度就提升很多了,怕影响理解)
参考资料
https://www.w3cschool.cn/shiro/
其他链接