一、什么是Shiro
Shiro是一个Java的安全(权限)框架,相对简单,对比Spring Security,没有Spring Security的功能强大。但是实际工作中可能不需要那么复杂 的东西,所以使用小而简单的Shiro就够了。其不仅适用于JavaSE环境,也适用于JavaEE环境。Shiro可以完成认证,授权,加密,会话管理,web集成,缓存等。
有哪些功能:
- Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;
- Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
- Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
- Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
- Web Support:Web 支持,可以非常容易的集成到 Web 环境;
- Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;
- Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
- Testing:提供测试支持;
- Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
- Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
记住一点,Shiro 不会去维护用户、维护权限;这些需要我们自己去设计 / 提供;然后通过相应的接口注入给 Shiro 即可。
二、什么是权限管理
权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。权限管理包括身份认证和授权两部分。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限才可访问。
三、shiro三大核心组件
Shiro有三大核心组件,即Subject、SecurityManager和Realm
- Subject: 相当于用户,为认证主体。应用代码直接交互的对象是Subject,Subject代表了当前的用户。包含Principals和Credentials两个信息。
Pricipals:代表身份。可以是用户名、邮件、手机号码等等,用来标识一个登陆主题的身份。
Credentials:代表凭证。常见的有密码、数字证书等等。
也就是说两者代表了认证的内容,最常见就是用户名密码了。用Shiro进行身份认证,其中就包括主体认证。
-
SecurityManager:用户安全管理,为安全管理员。是Shiro架构的核心。与Subject的所有交互都会委托给SecurityManager, Subject相当于是一个门面,而SecurityManager才是真正的执行者。它负责与Shiro 的其他组件进行交互。
-
Realm:连接数据,是一个域。充当了Shiro与应用安全数据间的“桥梁”。Shiro从Realm中获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm中获取相应的用户进行比较,来确定用户的身份是否合法;也需要从Realm得到用户相应的角色、权限,进行验证用户的操作是否能过进行,可以把Realm看成DataSource,即安全数据源。
也就是说对于我们而言,最简单的一个 Shiro 应用:
- 应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
- 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
从以上也可以看出,Shiro 不提供维护用户 / 权限,而是通过 Realm 让开发人员自己注入。
四、Shrio内部架构
- Subject: 主体,主体可以是任何可以与应用交互的“用户”
- SecurityManager: 是Shiro的核心,所有具体的交互都需通过SecurityManager进行,它管理所有的Subject,且负责进行认证授权,会话,及缓存的管理。
- Authenticator:负责主体认证。当用户尝试登录时,该逻辑被Authenticatior执行。Authenticator知道如何与一个或多个Realm协调来存储相关的用户。从Realm中获得的数据被用来验证用户的身份来保证用户确实是他们所说的他们是谁。
Autentication Strategy:如果不止一个Realm被配置,其会协调这些Realm来决定身份认证尝试成功或失败下的条件(比如,如果一个Realm成功,而其他的失败,是否该尝试成功?) - Authorizer:负责在应用程序中决定用户的访问控制。它是一种最终判定用户是否被允许做某事的机制。与Authenticator相似,Authorizer也知道如何协调多个后台数据源来访问角色恶化权限信息。Authorizer使用该信息来准确度的决定用户是否被允许执行给定的动作。
- SessionManager:知道如何去创建及管理用户Session生命周期来为所有环境下的用户提供一个强健的session体验。
- SessionDAO:代表SessionManager执行Session持久化操作。允许数据存储被插入到会员管理的基础之中。
- CacheManager:创建并管理其他Shiro组件使用的Cache实例声明周期。因为Shiro能访问许多后台数据源,由于身份验证、授权和会话管理,缓存在框架中一直是一流 的架构功能,用来在通过还是使用这些数据源时提高性能。
- Cryptograhy:是对企业安全框架的一个自然的补充。密码模块,shrio提高了一些常见的加密组件用于密码加密,解密等。
-------摘自zhuanlan.zhihu.com/p/274728884
五、环境搭建
- 导包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
- 配置类及对应的自定义的Realm
package com.wei.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class shiroConfig {
//三大核心要素
//1.shiroFilterFactoryBean,创建对象
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
factoryBean.setSecurityManager(securityManager);
//添加shiro的内置过滤器,拦截
// anon无需认证就可访问
//authc必须认证了才能访问
//user:必须拥有记住我功能才能用
//perms:拥有对某个资源的权限才能访问
//role:拥有某个角色权限才能访问
Map<String, String> filterMap = new LinkedHashMap<String, String>();
//设置访问权限,只有拥有对某个资源的权限才能访问,必须是user用户而且有add权限才能访问
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
// filterMap.put("/user/add","anon");
filterMap.put("/user/*","authc");
// filterMap.put("/user/*","authc"); user下的全部请求
factoryBean.setFilterChainDefinitionMap(filterMap);
//设置登录的请求,失败后会自动跳到登录页面
factoryBean.setLoginUrl("/toLogin");
//设置未授权页面跳转
factoryBean.setUnauthorizedUrl("/noauth");
return factoryBean;
}
//2.defaultWebSecurityManager,安全管理器,接管对象
@Bean("securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//管理realm
securityManager.setRealm(userRealm);
return securityManager;
}
//3.创建realm对象,需要自定义
@Bean("userRealm")
public UserRealm userRealm(){
return new UserRealm();
}
//整合shirodialect:用来整合shiro和thymleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
配置类之外,还需要相对应的自定义的Realm
package com.wei.config;
import com.wei.Service.UserService;
import com.wei.entity.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
//自定义的UserRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("===授权");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//授权
// info.addStringPermission("user:add");
//拿到当前登录的对象
Subject subject = SecurityUtils.getSubject();
User currentuser = (User) subject.getPrincipal();//拿到user对象
//设置用户权限(数据库查到的权限)
info.addStringPermission(currentuser.getPerms());
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("===认证");
//用户名,密码 数据库中取
// String username="root";
// String password="123456";
UsernamePasswordToken usertoken = (UsernamePasswordToken) token;
//连接数据库,去数据库查询
User user = userService.selectByUsername(usertoken.getUsername());
//用户名认证
if(user==null){
return null; //抛出异常,UnknownAccountException
}
//登录成功,设置session
Subject currentSubject=SecurityUtils.getSubject();
Session session = currentSubject.getSession();
session.setAttribute("loginuser",user);
//密码认证,由shiro做
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
}
- controlle配置
package com.wei.Controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class MyController {
@RequestMapping({"/","/index"})
public String toIndex(Model model){
model.addAttribute("msg","helloshiro");
return "index";
}
@RequestMapping("/user/add")
public String add(Model model){
return "user/add";
}
@RequestMapping("/user/update")
public String update(Model model){
return "user/update";
}
@RequestMapping("/toLogin")
public String toLogin(Model model){
return "login";
}
@RequestMapping("/login")
public String login(Model model,String username,String password){
//获取当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try{
subject.login(token);//执行登录的方法,如果没有异常就说明ok了
return "index"; //登录成功,返回首页
}catch (UnknownAccountException e){
//用户名不存在
model.addAttribute("msg","用户名错误");
return "login";
}catch (IncorrectCredentialsException e){
//密码不存在
model.addAttribute("msg","密码错误");
return "login";
}
}
@RequestMapping("/noauth")
@ResponseBody
public String unauthorized(){
return "未经授权,无法访问此页面";
}
}
- 首页
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<!--从session中判断值-->
<div th:if="session.loginuser==null">
<a th:href="@{/toLogin}">登录</a>
</div>
<!--如果有user:add权限就显示-->
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a> |
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">update</a>
</div>
</body>
</html>
- 登录页
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h1>登录</h1>
<hr>
<p th:text="${msg}" style="color: red"></p>
<form th:action="@{/login}">
<p>用户名:<input type="text" name="username"></p>
<p> 密码:<input type="password" name="password"></p>
<p><input type="submit"></p>
</form>
</body>
</html>