shiro入门基础教程

Posted by Sunfy on 2020-10-18
Words 1.9k and Reading Time 9 Minutes
Viewed Times
Viewed Times
Visitors In Total

shiro授权框架,个人理解主要适合单体应用,分布式应用和微服务架构模式下需要改造重新实现一些功能才能使用。

基础环境

  1. maven依赖
1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
</dependency>
<!-- shiro使用的是slf4j日志门面 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
  1. 基本配置类
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* @Description:
* @Author: sunfy
* @Date: 2020/10/19 上午11:18
*/
@Configuration
public class shiroConfig {
/**
* Realm 系统资源
* MyRealm类中已经注入,此处不需要重复
*
*/
// @Bean
// public Realm myRealm() {
// return new MyRealm();
// }
/**
* SecurityManager 流程控制
* 构造方法的形式引入,创建对象的时候就可以引入Realm
*/
@Bean
public DefaultWebSecurityManager mySecurityManager(AuthorizingRealm myRealm, AuthenticatingRealm mobileRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//密码加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// 算法名
matcher.setHashAlgorithmName("MD5");
// 迭代次数
matcher.setHashIterations(3);
// private String hashAlgorithm;
// private int hashIterations;
// private boolean hashSalted; 已经过时
// private boolean storedCredentialsHexEncoded; 设置默认的
myRealm.setCredentialsMatcher(matcher);
mobileRealm.setCredentialsMatcher(matcher);
securityManager.setRealms(Arrays.asList(myRealm, mobileRealm));
// securityManager.setRealm(myRealm);
// 设置认证策略
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
// 设置认证策略为有一个成功即可
authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
// 设置所有的认证策略都返回成功
// authenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());
// 设置第一个认证策略返回成功后不再认证后续的
// authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
authenticator.setRealms(Arrays.asList(myRealm, mobileRealm));
securityManager.setAuthenticator(authenticator);
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
Cookie cookie = new SimpleCookie("rememberMe");
cookie.setHttpOnly(true);
cookie.setMaxAge(5);
cookieRememberMeManager.setCookie(cookie);
/**
* 分布式环境需要自己改造以下功能
*/
// 设置记住我功能
securityManager.setRememberMeManager(cookieRememberMeManager);
// session 共享
// securityManager.setSessionManager();
// 缓存
MemoryConstrainedCacheManager memoryConstrainedCacheManager = new MemoryConstrainedCacheManager();
securityManager.setCacheManager(memoryConstrainedCacheManager);
return securityManager;
}
/**
* ShiroFilterFactoryBean 请求过滤器
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager mySecurityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(mySecurityManager);
// 配置路径经过过滤器
Map<String, String> fifterMap = new HashMap<>();
// key是ant路径,value配置shiro的默认过滤器
// shiro的默认过滤器,配置DefaultFilter
// anon(AnonymousFilter.class), 匿名过滤器
// authc(FormAuthenticationFilter.class), 认证(登录)使用
// authcBasic(BasicHttpAuthenticationFilter.class), 认证不通过跳转登录页
// authcBearer(BearerHttpAuthenticationFilter.class),
// logout(LogoutFilter.class),
// noSessionCreation(NoSessionCreationFilter.class),
// perms(PermissionsAuthorizationFilter.class),
// port(PortFilter.class),
// rest(HttpMethodPermissionFilter.class),
// roles(RolesAuthorizationFilter.class),
// ssl(SslFilter.class),
// user(UserFilter.class),
// invalidRequest(InvalidRequestFilter.class);
// fifterMap.put("/mobile/**", "authc, perms[mobile]");
fifterMap.put("/salary/**", "authc, perms[salary]");
// 相当与登出方法 使用shiro提供的logout过滤器
fifterMap.put("/common/logout", "logout");
// 设置登录页
factoryBean.setLoginUrl("/index.html");
// 定制未授权页面
factoryBean.setUnauthorizedUrl("/common/unAuth");
// iml文件中进行相应的配置,也可以直接手动写
// factoryBean.setFilterChainDefinitions("/mobile/**=authc");
factoryBean.setFilterChainDefinitionMap(fifterMap);
return factoryBean;
}
}

实现登录验证功能

  1. 创建Realm对象,继承 AuthorizingRealm
  2. 实现父类的方法
    1. 授权方法doGetAuthorizationInfo
    2. 认证方法doGetAuthenticationInfo
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// Realm配置文件
import com.sunfy.shiroWeb.bean.UserBean;
import com.sunfy.shiroWeb.service.UserService;
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.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
/**
* @Description:
* @Author: sunfy
* @Date: 2020/10/19 上午11:21
*/
@Configuration
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
private Logger logger = LoggerFactory.getLogger(MyRealm.class);
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
logger.info(">>>entered MyRealm doGetAuthorizationInfo method");
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 获得当前用户
UserBean userBean = (UserBean) principalCollection.asList().get(0);
// 绑定角色和资源
simpleAuthorizationInfo.addRoles(userBean.getUserRoles());
simpleAuthorizationInfo.addStringPermissions(userBean.getUserPerms());
return simpleAuthorizationInfo;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
logger.info("<<<entered MyRealm doGetAuthenticationInfo method");
// 获得当前用户
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
String userName = userToken.getUsername();
// 获得数据库中密码,来跟当前用户进行比对,认证。
UserBean userBean = userService.queryUserByName(userName);
// 如果没有查到,没有这个用户
if (userBean == null) {
// shiro判断 抛出 UnknownAccountException 异常提醒
return null;
}
ByteSource salt = ByteSource.Util.bytes("salt");
// 返回 AuthenticationInfo 完成认证流程(密码比较认证shiro进行认证)
SimpleAuthenticationInfo simpleAuthenticationInfo =
new SimpleAuthenticationInfo(userBean, userBean.getUserPass(),salt,"myRealm");
return simpleAuthenticationInfo;
}
}

认证错误访问情况设置

设置登录页、登录成功也、未授权页面

1
2
3
4
// 设置登录页
factoryBean.setLoginUrl("/index.html");
// 定制未授权页面
factoryBean.setUnauthorizedUrl("/common/unAuth");

登出

1
2
// 相当与登出方法 使用shiro提供的logout过滤器
fifterMap.put("/common/logout", "logout");

授权功能

  1. 控制主页上按钮的访问权限

    currentUser.getPricipal() 来自 MyRealmdoGetAuthenticationInfo认证返回的simpleAuthenticationInfo对象的第一个属性

  2. 控制后台资源路径的访问权限

    1. 方法1:硬编码的方法,自行判断权限。

      1
      2
      3
      4
      5
      6
      7
      8
      @RequestMapping("query")
      public String query(){
      Subject subject = SecurityUtils.getSubject();
      if (subject.isPermitted("mobile")) {
      return "mobile";
      }
      return "error";
      }
    2. 方法2:使用shiro提供的perms过滤器,集中配置权限信息

      1
      2
      fifterMap.put("/mobile/**", "authc, perms[mobile]");
      fifterMap.put("/salary/**", "authc, perms[salary]");

      错误补充机制:没有权限就会进入到ShiroFilterFactoryBean中配置的UnauthorizedUrl

    3. 方法3:使用shiro提供的注解,实现方法级别的权限控制。

      1
      2
      3
      4
      5
      @RequiresAuthentication 需要完成用户登录
      @RequiresGuest 未登录用户可以访问,登录用户不可访问
      @RequiresPermissions 需要有对应资源权限
      @RequiresRoles 需要有对应的角色
      @RequiresUser 需要完成用户登录并且实现了记住我功能

      错误补充机制:没有权限会抛出异常(500错误)

      如果基本框架使用的是Spring Boot可以捕获异常处理:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      import org.apache.shiro.authz.AuthorizationException;
      import org.springframework.web.bind.annotation.ExceptionHandler;
      import org.springframework.web.bind.annotation.RestControllerAdvice;
      /**
      * @Description: springBoot方式捕获异常处理
      * @Author: sunfy
      * @Date: 2020/10/19 下午3:39
      */
      @RestControllerAdvice
      public class MyExceptionHandler {
      @ExceptionHandler(AuthorizationException.class)
      public Object unAuth(){
      return "没有资源访问权限";
      }
      }

密码加密

shiro会获得一个CredentialsMatcher对象,来进行密码比对。

如果想使用MD 5加密,可以使用HashedCredentialsMatcherMd5CredentialsMatcher已经过期

1
2
3
4
5
6
7
8
9
10
11
//密码加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// 算法名
matcher.setHashAlgorithmName("MD5");
// 迭代次数
matcher.setHashIterations(3);
// private String hashAlgorithm; 算法名
// private int hashIterations; 迭代次数
// private boolean hashSalted; 已经过时
// private boolean storedCredentialsHexEncoded; 设置默认的
myRealm.setCredentialsMatcher(matcher);

数据库数据加密方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
/**
* @Description: 密码加密
* @Author: sunfy
* @Date: 2020/10/19 下午4:40
*/
public class PasswordEncoder {
public static String encoder(String password) {
SimpleHash simpleHash = new SimpleHash("MD5", ByteSource.Util.bytes(password), ByteSource.Util.bytes("salt"), 3);
return simpleHash.toString();
}
public static void main(String[] args) {
System.out.println(encoder("manager"));
}
}

多Realm认证

可用于实现用户名和手机号都可以登录。

1
2
3
4
5
6
7
8
9
10
org.apache.shiro.authz.ModularRealmAuthorizer
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}

实现多个Realm完成不同的认证功能,通过增加一个Realm,事项按照手机号也可以登录的功能

注意可以设置不同的认证策略:

1
2
3
4
5
6
7
8
9
10
11
12
// 创建认证策略
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
// 设置认证策略为至少有一个成功
authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
// 设置所有的认证策略都返回成功
authenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());
// 设置第一个认证策略返回成功后不再认证后续的
authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
// 设置多个Realm
authenticator.setRealms(Arrays.asList(myRealm, mobileRealm));
// 设置认证策略
securityManager.setAuthenticator(authenticator);

记住我功能

1
2
// 记住我功能
token.setRememberMe(true);

记住我功能对应了默认的user过滤器。

怎么设置记住我时常,以及记住我功能记录保存位置

1
2
3
4
5
6
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
Cookie cookie = new SimpleCookie("rememberMe");
cookie.setHttpOnly(true);
cookie.setMaxAge(5);
cookieRememberMeManager.setCookie(cookie);
securityManager.setRememberMeManager(cookieRememberMeManager);

会话管理

1
securityManager.setSessionManager();

认证缓存

1
2
MemoryConstrainedCacheManager memoryConstrainedCacheManager = new MemoryConstrainedCacheManager();
securityManager.setCacheManager(memoryConstrainedCacheManager);

备注:记住我、会话管理以及认证缓存,都可以通过扩展对应的manager接口的方式,实现自己的灵活扩展,比如将对应的数据信心存储在redis中。

Realm类型关系图

image-20201020144848997


Copyright 2021 sunfy.top ALL Rights Reserved

...

...

00:00
00:00