1、shiro基本概念
Shiro是Apache组织下的一个开源的安全框架,常常用来做:安全认证、权限管理、会话管理、加密
1.1、安全认证
可用于验证用户的身份凭证,如用户名和密码。通过配置 Shiro 的 Realm(领域)来自定义身份验证的逻辑,通过 Subject 对象进行身份验证,可以获取用户的身份信息和执行相关操作
1.2、权限管理
可以通过配置 Shiro 的 Realm 来定义用户的角色和权限信息,或者使用注解方式进行权限控制。通过 Subject 对象进行权限检查,可以判断用户是否具有某个角色或权限,并根据结果进行相应的处理
1.3、会话管理
shiro 提供了会话管理的功能,用于跟踪用户的登录状态和管理用户的会话信息。Shiro 的会话管理可以基于 Web 容器的 HttpSession,也可以使用自己的非 Web 环境会话管理(redis等)
1.4、加密
对密码进行加密和解密操作,以保护用户的密码安全。Shiro 支持常用的加密算法,如MD5、SHA、AES等。你可以根据需求选择适当的加密算法进行使用
2、shiro架构
2.1、主体(Subject)
Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权
2.2、安全管理器(Security Manager)
SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。
SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口
2.3、认证器(Authenticator)
Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器
2.4、授权器(Authorizer)
Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限
2.5、领域(Realm)
Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。
2.6、会话管理器(SessionManager)
会话管理器负责管理主体的会话,跟踪用户的登录状态和管理会话数据。
在 Shiro 中,会话管理器可以基于 Web 容器的 HttpSession,也可以使用自己的非 Web 环境会话管理
3、认证
在shiro中,用户需要提供principlas(身份)和credentials(证明)给shiro,从而应用能验证用户身份
3.1、认证流程
- 提交身份凭证:用户在应用程序的登录页面或其他身份验证入口提交身份和凭证,例如用户名和密码
- 创建 Subject 对象:应用程序根据用户提交的身份凭证创建一个 Subject 对象,代表当前与应用程序交互的用户
- 提交身份凭证给认证器:Subject 对象将身份凭证提交给 Shiro 的认证器(Authenticator)进行验证
- 认证器验证身份凭证:认证器对身份凭证进行验证,通常是通过比对凭证与存储在数据源中的用户信息进行匹配。认证器可以使用一个或多个 Realm 来获取用户信息并进行验证。Realm 对获取到的用户信息与提交的身份凭证进行比对验证,判断凭证是否有效
- 结果处理:如果身份验证成功,认证器将成功的身份信息存储在 Subject 对象中,以便后续使用
3.2、认证实现
3.2.1、自定义Realm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
public class CustomerRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("=================="); return null; }
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String principal = (String) token.getPrincipal(); System.out.println(principal);
String username="zhangsan"; String password="123456"; if(username.equals(principal)){ SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,password,this.getName()); return simpleAuthenticationInfo; } return null; } }
|
3.2.2、用定义的Realm进行认证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
public class TestAuthenticatorCusttomerRealm {
public static void main(String[] args) { DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(new CustomerRealm());
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123"); try { subject.login(token); System.out.println("登录成功~~"); } catch (UnknownAccountException e) { e.printStackTrace(); System.out.println("用户名错误!!"); }catch (IncorrectCredentialsException e){ e.printStackTrace(); System.out.println("密码错误!!!"); } } }
|
4、授权
4.1、授权流程
认证成功后,Shiro会将用户的身份信息保存在Subject对象中。一旦用户通过认证,就可以进行授权操作。授权是基于用户的身份和角色进行的,用于确定用户是否有权进行特定的操作或访问特定的资源。Shiro提供了基于角色的访问控制(Role-Based Access Control)和基于权限的访问控制(Permission-Based Access Control)两种授权方式。授权的核心是Realm。Realm是用于获取安全数据的组件
- 用户发起访问请求。
- Shiro的Subject对象获取当前用户的身份信息,并创建相应的Principal对象。
- Subject将Principal对象传递给SecurityManager进行授权操作。
- SecurityManager委托Realm获取用户的角色和权限信息。
- Realm根据Principal对象从数据源(如数据库)中获取用户的角色和权限信息。
- Realm将获取的角色和权限信息返回给SecurityManager。
- SecurityManager根据获取的角色和权限信息进行授权判断,确定用户是否有权访问资源。
- 根据授权结果,SecurityManager返回授权成功或失败的结果给Subject对象。
- Subject根据授权结果执行相应的操作,允许或拒绝用户访问受限资源。
4.2、授权方式
4.2.1、基于角色的访问控制
1 2 3
| if(subject.hasRole("admin")){ }
|
4.2.2、基于资源的访问控制
1 2 3 4 5 6
| if(subject.isPermission("user:update:01")){ } if(subject.isPermission("user:update:*")){ }
|
权限字符串的规则是:资源标识符:操作:资源实例标识符,意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用通配符
用户创建权限:user:create,或user:create:
用户修改实例001的权限:user:update:001
用户实例001的所有权限:user:*:001
4.3、授权实现
4.3.1、定义Reaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
public class CustomerMd5Realm extends AuthorizingRealm {
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String primaryPrincipal = (String)principals.getPrimaryPrincipal(); System.out.println("身份信息: "+primaryPrincipal);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.addRole("admin"); simpleAuthorizationInfo.addRole("user"); simpleAuthorizationInfo.addStringPermission("user:*:01"); simpleAuthorizationInfo.addStringPermission("prodect:*");
return simpleAuthorizationInfo; }
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
} }
|
4.3.2、授权
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class TestCustomerMd5RealmAuthenicator {
public static void main(String[] args) { if (subject.isAuthenticated()){
System.out.println(subject.hasRole("admin")); System.out.println(subject.hasAllRoles(Arrays.asList("admin", "user"))); System.out.println(subject.hasAllRoles(Arrays.asList("admin", "manager"))); boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "user", "manager")); for (boolean aBoolean : booleans) { System.out.println(aBoolean); }
System.out.println("====这是一个分隔符====");
System.out.println("权限:"+subject.isPermitted("user:update:01")); System.out.println("权限:"+subject.isPermitted("prodect:update:02"));
boolean[] permitted = subject.isPermitted("user:*:01", "user:update:02"); for (boolean b : permitted) { System.out.println(b); }
boolean permittedAll = subject.isPermittedAll("prodect:*:01", "prodect:update:03"); System.out.println(permittedAll); } } }
|
5、会话管理(cacheManager)
5.1、shiro会话管理实现(EhCache)
5.1.1、引入依赖
1 2 3 4 5 6
| <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.5.3</version> </dependency>
|
5.1.2、开启缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Bean public Realm getRealm(){ CustomerRealm customerRealm = new CustomerRealm(); HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); credentialsMatcher.setHashAlgorithmName("MD5"); credentialsMatcher.setHashIterations(1024); customerRealm.setCredentialsMatcher(credentialsMatcher); customerRealm.setCachingEnabled(true); customerRealm.setAuthorizationCachingEnabled(true); customerRealm.setAuthorizationCachingEnabled(true); customerRealm.setCacheManager(new EhCacheManager()); return customerRealm; }
|
6、springboot整合shiro
6.1、引入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.4.0</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
<dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
|
6.2、配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| shiro.enabled=true
shiro.web.enabled=true
shiro.loginUrl=/login
shiro.successUrl=/index
shiro.unauthorizedUrl=/unauthorized
shiro.sessionManager.sessionIdUrlRewritingEnabled=true
shiro.sessionManager.sessionIdCookieEnabled=true
|
6.3、配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| @Configuration public class ShiroConfig {
@Bean public Realm realm(){ TextConfigurationRealm realm = new TextConfigurationRealm(); realm.setUserDefinitions("nihui=123,user admin=123,admin"); realm.setRoleDefinitions("admin=read,write user=read"); return realm; }
@Bean public ShiroFilterChainDefinition shiroFilterChainDefinition(){ DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); chainDefinition.addPathDefinition("/login","anon"); chainDefinition.addPathDefinition("/doLogin","anon"); chainDefinition.addPathDefinition("logout","logout"); chainDefinition.addPathDefinition("/**","authc"); return chainDefinition; }
@Bean public ShiroDialect shiroDialect(){ return new ShiroDialect(); } }
|
6.4、登录接口控制类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Controller public class UserController { @PostMapping("/doLogin") public String doLogin(String username, String password, Model model){ UsernamePasswordToken token = new UsernamePasswordToken(username,password); Subject subject = SecurityUtils.getSubject(); try{ subject.login(token); }catch (AuthenticationException e){ model.addAttribute("error","用户名密码输入错误"); return "login"; } return "redirect:/index"; } @RequiresRoles("admin") @GetMapping("/admin") public String admin(){ return "admin"; }
@RequiresRoles(value = {"admin","user"},logical = Logical.OR) @GetMapping("/user") public String user(){ return "user"; } }
|
6.5、编写页面代码
在对应的目录下面建立五个页面分别表示用户登录页面、首页、用户页面、管理员页面、认证错误页面