Day21 Shiro 1
uwupu 啦啦啦啦啦

Shiro

https://shiro.apache.org/

介绍

  • Shiro与SpringSecurity类似;
  • Apache Shiro是一个Java的安全(权限)框架;
  • Shiro可以完成认证、授权、加密、会话管理、Web集成、缓存等。

Shiro架构

subject:应用代码直接交互的对象Subject,即Shiro的对外API核心就是Subject,Subject代表了当前的用户。

SecurityManager:安全管理器,管理所有的Subject,负责与Shiro其他组件交互。

Realm:Shiro从Realm获取安全数据(用户,角色,权限)。SecurityManager要验证用户身份,需要从Realm获取相应的用户进行比较。

第一个Shiro程序

依赖

pom.xml

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
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.9.1</version>
</dependency>
<!-- Shiro uses SLF4J for logging. We'll use the 'simple' binding
in this example app. See http://www.slf4j.org for more info. -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>


<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.21</version>
</dependency>
</dependencies>

shiro.ini

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# =============================================================================
# Tutorial INI configuration
#
# Usernames/passwords are based on the classic Mel Brooks' film "Spaceballs" :)
# =============================================================================

# -----------------------------------------------------------------------------
# Users and their (optional) assigned roles
# username = password, role1, role2, ..., roleN
# -----------------------------------------------------------------------------
[users]
root = secret, admin
guest = guest, guest
lunch = lunch, eater
water = water, drinker

# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# -----------------------------------------------------------------------------
[roles]
admin = *
eater = eat:*
drinker = drink:kola:nosugar

主程序

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
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Hello {
private static final transient Logger log = LoggerFactory.getLogger(Hello.class);

public static void main(String[] args) {
log.info("My First Apache Shiro Application");

//1. 获得安全管理器Factory
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2. 获得安全管理器实例
SecurityManager securityManager = factory.getInstance();
//3. 设置安全管理器
SecurityUtils.setSecurityManager(securityManager);

Subject currentUser = SecurityUtils.getSubject();//获取当前用户

//若当前用户没有登录
if (!currentUser.isAuthenticated()){
UsernamePasswordToken token = new UsernamePasswordToken("root","secret");
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (UnknownAccountException e) {
log.info("未知用户名");
}catch (IncorrectCredentialsException ice){
log.info("错误的密码");
}catch (LockedAccountException lae){
log.info("锁定的账户");
}
}




Session session = currentUser.getSession();//获取当前用户的Session
session.setAttribute("key","value");//设置key和value
String value = (String) session.getAttribute("key");//获取key-value
if ("value".equals(value)){
log.info("正确:"+value);
}



log.info("当前用户名:"+currentUser.getPrincipal());//获得当前用户名

//当前用户是否拥有角色
if (currentUser.hasRole("admin")){
log.info("管理员?");
}else {
log.info("不是管理员");
}

//测试单权限
if (currentUser.isPermitted("eat:Obsidian")){
log.info("请不要饮食过度");
}else {
log.info("你不能吃饭。");
}

//权限组
if (currentUser.isPermitted("drink:SnowGreen")){
log.info("请不要过度饮雪");
}else{
log.info("你不能喝格林雪");
}
}
}

运行结果

1
2
3
4
5
6
7
8
9
[main] INFO com.yn.Hello - My First Apache Shiro Application
[main] INFO org.apache.shiro.session.mgt.AbstractValidatingSessionManager - Enabling session validation scheduler...
[main] INFO com.yn.Hello - 正确:value
[main] INFO com.yn.Hello - 当前用户名:root
[main] INFO com.yn.Hello - 管理员?
[main] INFO com.yn.Hello - 请不要饮食过度
[main] INFO com.yn.Hello - 请不要过度饮雪

Process finished with exit code 0

Subject对象的方法

在Shiro里,Subject对象用来表示一个用户;

可以使用SecurityUtils.getSubject()方法获得这个对象。

方法

  • isAuthenticated():返回boolean,当前用户是否经过验证(登录);
  • login(token):登录一个用户,通过try-catch捕获登录错误时的操作
    • UnknownAccountException:没有这个用户
    • IncorrectCredentialsException:错误的密码
    • LockedAccountException:锁定的账户
  • getSession():获取当前用户的Session。可以使用Session的setAttribute方法和getAttribute方法存或取值。
  • getPrincipal():获取当前用户名;
  • hasRole(“admin”):当前用户是否拥有角色admin;
  • isPermitted(“eat:Obsidian”):是否被允许有“eat:Obsidian”权限。另外一个如:**isPermitted(“drink:Cola”)**则为是否拥有drink:Cola权限;
  • logout():登出。

SpringBoot与Shiro集成

可能是Spring与Shiro集成?!

简易配置

  1. 创建ShiroConfig配置类和UserRealm;

    image-20220914084926404

  2. UserRealm类继承AuthorizingRealm对象,重写相关方法;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //一个自定义的UserRealm
    //继承AuthorizingRealm对象
    public class UserRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    System.out.println("执行了 -> 授权 PrincipalCollection");
    //授权
    return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    System.out.println("执行了 -> 认证 AuthenticationToken");
    //有用户登录时会进入这里
    //这里负责判断账号密码正确与否
    return null;
    }
    }
  3. ShiroConfig下创建三个@Bean,

    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
    @Configuration
    public class ShiroConfig {

    //@Bean方式 创建 Realm 对象 自定义类
    @Bean
    public UserRealm userRealm(){
    return new UserRealm();
    }
    //DefaultWebSecurityManager
    @Bean
    public DefaultWebSecurityManager getDefaultWebSessionManager(@Qualifier("userRealm") UserRealm realm){
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

    //关联UserRealm
    securityManager.setRealm(realm);


    return securityManager;
    }
    //ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(
    @Qualifier("getDefaultWebSessionManager")DefaultWebSecurityManager securityManager){

    ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
    //关联securityManager 设置安全管理器
    bean.setSecurityManager(securityManager);

    //这里进行一些授权的配置,如:url与角色的绑定

    //配置登录API和登录页相关信息

    return bean;
    }
    }

    分别返回UserRealm、DefaultWebSecurityManager、ShiroFilterFactoryBean三个对象,其中:

    1. UserRealm直接新建一个刚刚创建的Realm并返回就可;
    2. DefaultWebSecurityManager,使用@Qualifier注解将刚刚创建的Realm Bean作为方法的参数,新建一个DefaultWebSecurityManager对象,使用setRealm方法绑定刚刚绑定的Realm,然后返回;
    3. ShiroFilterFactoryBean,使用@Qualifier注解将刚刚创建的DefaultWebSecurityManager Bean作为方法的参数,新建一个ShiroFilterFactoryBean对象,使用setSecurityManager方法绑定刚刚创建的DefaultWebSecurityManager,然后返回。
  4. 配置结束。

配置

UserRealm

创建一个UserRealm类,继承AuthorizingRealm类,重写:

  • protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
  • protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)

两个方法。

doGetAuthorizationInfo(PrincipalCollection principals)

这个方法负责授权

下面的方法执行结束后,会执行这个方法,负责为登录的用户添加权限(加载权限)。

这里要做的事:

  1. 创建SimpleAuthorizationInfo对象。

    1
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
  2. 从principals中获取用户的权限列表。(下面的方法为当前用户配置了principals属性,这里指用户的User对象,这里通过User.getPerms()获得用户的权限)

    1
    2
    3
    //拿到当前登录的对象
    Subject subject = SecurityUtils.getSubject();
    User currentUser = (User) subject.getPrincipal();
  3. 为用户添加权限

    1
    2
    //设置当前用户的权限
    info.addStringPermission(currentUser.getPerms());//currentUser.getPerms()是从user对象获取用户的权限
  4. 返回AuthorizationInfo对象。

示例
1
2
3
4
5
6
7
8
9
10
11
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("执行了 -> 授权 PrincipalCollection");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//拿到当前登录的对象
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal();
//设置当前用户的权限
info.addStringPermission(currentUser.getPerms());
return info;
}

doGetAuthenticationInfo(AuthenticationToken token)

这个方法负责认证

有用户登录时会进入这个方法。

这里要做的事:

  • 从数据库中获取用户相关信息;

  • 判断用户是否存在,不存在返回null;

  • 创建一个AuthenticationInfo类型的对象,将主体对象和密码传入,然后将该对象返回。

    (将密码交给Shiro,由Shiro完成密码判断和其他操作。)

示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//一旦进行了登录,就会执行这个方法
System.out.println("执行了 -> 认证 AuthenticationToken");
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
//从数据库获取用户 userService是service层用于与mapper层交互的对象。
User user = userService.queryUserByUsername(usernamePasswordToken.getUsername());
if(user==null){
//没有这个用户
return null;//返回null,在登录方法(这里是Controller的login POST中)会抛出异常UnknownAccountException
}

//判断密码是否正确
//加密方式: MD5 MD5盐值加密
//MD5盐值加密:将密码和用户名都进行加密
//密码认证交给shiro处理 有加密功能
//参数1表示当前用户的主体对象,这里user是主体对象。用于在doGetAuthorizationInfo(PrincipalCollection principals)方法中获取用户的一些信息(如:权限)。
return new SimpleAuthenticationInfo(user, user.getPassword(), "");
}
  1. 首先将AuthenticationToken token强制转换为UsernamePasswordToken对象,然后就可以获取到登录用户的一些信息
    • getUsername():用户名
    • getPassword():密码
  2. 然后通过用户名从数据库中获取用户;
  3. 最后创建一个验证对象将密码传入然后将对象返回。

ShiroConfig

创建一个@Configuration配置类,用于存放Shiro类的一些@Bean配置。

(这里设置类名为ShiroConfig用于示例)

UserRealm

1
2
3
4
@Bean
public UserRealm userRealm(){
return new UserRealm();
}

创建UserRealm对象并返回。

DefaultWebSecurityManager

1
2
3
4
5
6
7
8
9
@Bean
public DefaultWebSecurityManager getDefaultWebSessionManager(@Qualifier("userRealm") UserRealm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

//关联UserRealm
securityManager.setRealm(realm);

return securityManager;
}

创建Bean DefaultWebSecurityManager getDefaultWebSessionManager(@Qualifier("userRealm")用于创建SecurityManager,绑定realm,并返回对象。

  • 创建SecurityManager对象 (指定SecurityManager)
  • 绑定realm (指定realm对象)

ShiroFilterFactoryBean

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
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(
@Qualifier("getDefaultWebSessionManager")DefaultWebSecurityManager securityManager){

ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//关联securityManager 设置安全管理器
bean.setSecurityManager(securityManager);

/**
* 添加Shiro的内置过滤器
* 一些用于示例的角色
* anon:无需认证;
* authc:必须认证
*/
//进行配置
Map<String,String> filterMap = new LinkedHashMap<>();//配置
bean.setFilterChainDefinitionMap(filterMap);
//授权 url和角色的绑定
filterMap.put("/user/add","anon");//设置url可访问角色
filterMap.put("/user/update","authc");


//配置登录API和登录页相关信息
//设置登录请求
bean.setLoginUrl("/login");
//设置用户进入未经授权的页面要跳转的页面
bean.setUnauthorizedUrl("/noauth");

//返回对象
return bean;
}
  • 创建ShiroFilterFactoryBean对象,并将SecurityManager绑定到对象;

    • ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
      //关联securityManager 设置安全管理器
      bean.setSecurityManager(securityManager);
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      - 进行“授权”相关的配置:绑定url和角色;

      - ```java
      //进行配置
      Map<String,String> filterMap = new LinkedHashMap<>();//配置
      bean.setFilterChainDefinitionMap(filterMap);
      //授权 url和角色的绑定
      filterMap.put("/user/add","anon");//设置url可访问角色
      filterMap.put("/user/update","authc");
  • 配置登录API和登录页相关配置

    • //配置登录API和登录页相关信息
      //设置登录请求
      bean.setLoginUrl("/login");
      //设置用户进入未经授权的页面要跳转的页面
      bean.setUnauthorizedUrl("/noauth");
      
      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

      - 返回ShiroFilterFactoryBean对象。

      ### shiro.ini配置

      ```ini
      # =======================
      # Shiro INI configuration
      # =======================

      [main]
      # 放一些配置

      # 配置密码编码:sha256
      sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
      iniRealm.credentialsMatcher = $sha256Matcher


      [users]
      # 放一些静态用户 ,一般在数据库中,不在这里编写。
      # 用户名 = 密码,角色1,角色2
      yn = 8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92,admin,eater


      [roles]
      # 这里放角色以及一些权限
      # 角色名 = 权限

      # 访客可以注册账户,登录账户
      guest = user:reg,login

      # 登录用户可以更新自己的信息
      user = user:update

      # vip1可以自定义主页CSS
      vip1 = user:index:update:css

      # vip2可以自定义主页HTML和CSS
      vip2 = user:index:update:html,css

      # vip3可以自定义主页的HTML、CSS和JS(JS需要经过审核)
      vip3 = user:index:update:*

      # admin可以为所欲为
      admin = *

      #

      [urls]
      # 为url绑定 角色/权限

      # 允许任何人访问根目录
      / = anon
      # 允许任何人创建用户
      /user/create = anon
      # 允许任何人进行登录
      /user/login = anon
      # 仅允许登录的用户进入/user/根目录下页面
      /user/* = authc
      # 仅允许管理员且被认证的用户进入/admin/下所有页面
      /admin/** = authc,roles[admin]


      # 拥有user:index:update:html权限的用户可以进入/user/index/html
      /user/index/html = perms[user:index:update:html]
      /user/index/css = perms[user:index:update:css]
      /user/index/js = perms[user:index:update:js]

一些Filter

Filter NameClass(基于org.apache.shiro.web.filter)介绍
anonauthc.AnonymousFilter匿名用户
authcauthc.FormAuthenticationFilter认证用户
authcBasicauthc.BasicHttpAuthenticationFilter
authcBearerauthc.BearerHttpAuthenticationFilter
invalidRequestInvalidRequestFilter无效请求
logoutauthc.LogoutFilter登出请求
noSessionCreationsession.NoSessionCreationFilter在此页面不能创建Session
permsauthz.PermissionsAuthorizationFilter当用户具有指定权限,则允许访问
portauthz.PortFilter位于特定端口上的过滤器。
restauthz.HttpMethodPermissionFilterRESTful环境
rolesauthz.RolesAuthorizationFilter判断用户拥有指定角色
sslauthz.SslFilter必须使用ssl访问
userauthc.UserFilter若是已知用户且已登录,则允许访问该页面,否则不允许访问。

Thymeleaf与Shiro集成

https://github.com/theborakompanioni/thymeleaf-extras-shiro

依赖

HTML

1
2
<html xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<!-- 已经不能用了 后续如果找到能用的 再改 -->

pom.xml

1
2
3
4
5
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>${thymeleaf-shiro.version}</version>
</dependency>

配置类

1
2
3
4
5
//整合ShiroDialect:用来整合shiro和thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}

添加一个返回ShiroDialect对象的@Bean方法就可。

可以放在ShrioConfig类下。

作用

  1. 可以在Thymeleaf下的HTML文件里添加Shiro为JSP设计的页面控制标签;

    1
    <shiro:guest>未登录的标志</shiro:guest>
  2. 可以将标签作为属性写在HTML元素中。

    1
    <p shiro:guest>未登录的标志2</p>

一些标签/属性

guest

当前用户是访客,未登录状态;

1
<shiro:guest>请登录</shiro:guest>
1
<p shiro:guest>请登录</p>

user

当前用户已登录;

1
<shiro:user>Welcome back!张三</shiro:user>
1
<p shiro:user>Welcome back!张三</p>

authenticated

在当前会话期间,用户通过身份验证。(与user不同,authenticated不能在只有remember-me没有会话的情况下有效,如:重新打开浏览器/会话关闭)

1
<shiro:authenticated>身份通过验证,可以访问隐私内容</shiro:authenticated>
1
<p shiro:authenticated>身份通过验证,可以访问隐私内容</p>

notAuthenticated

在当前会话期间未成功验证。(与authenticated相反)

1
<shiro:notAuthenticated>需要二次验证才能访问当前内容 / 请登录</shiro:notAuthenticated>
1
<p shiro:notAuthenticated>需要二次验证才能访问当前内容 / 请登录</p>

principal

将输出在后端配置的principal属性,toString()方法的返回值;

1
2
3
4
5
6
7
<shiro:principal></shiro:principal>

<shiro:principal/>

<shiro:principal type="com.foo.User" property="firstName"/>

<p shiro:principal></p><!-- thymeleaf-extras-shiro插件不支持type属性和property属性? -->

类型type

指定principal的类型,指定后将按照不同方式进行输出。不指定则按照toString输出。

1
<principal type="java.lang.Integer"/>

属性值property

若principal是一个对象,可以通过这种方式指定要输出的对象的属性值。

1
<shiro:principal type="User" property="lastname"/>

hasRole

用户拥有指定角色时。

1
<shiro:hasRole name="admin">管理员你好</shiro:hasRole>
1
<p shiro:hasRole="admin">管理员你好</p>

lacksRole

用户没有指定角色时。

1
<shiro:lacksRole name="admin">你不是管理员吧?</shiro:lacksRole>
1
<p shiro:lackRole="admin">闲杂人等不得入内</p>

缺少admin角色的用户会看到上面的内容。

hasAnyRoles

用户拥有以下任意角色时。

1
<shiro:hasAnyRoles name="developer, project manager, administrator">你好,管理员</shiro:hasAnyRoles>
1
<p shiro:hasAnyRoles="developer, project manager, administrator">你好,管理员</p>

当访问这是developer, project manager, administrator其中任意一种角色时,可以看到上面内容。

hasAllRoles

用户拥有以下所有角色时。

1
<shiro:hasAllRoles name="developer, project manager, administrator">拥有前面所有角色的用户能看得见</shiro:hasAllRoles>
1
2
3
<p shiro:hasAllRoles="developer, project manager, administrator">
拥有前面所有角色的用户能看得见
</p>

hasPermission

用户拥有具有指定的权限。(暗示:通过角色获取的权限)

1
2
3
<shiro:hasPermission name="user:create">
<a href="createUsers">Create a new User</a>
</shiro:hasPermission>
1
2
3
<p shiro:hasPermission="user:create">
<a href="createUsers">Create a new User</a>
</p>

lacksPermission

用户缺少指定权限时。

1
2
3
<shiro:lacksPermission name="user:delete">
Sorry, you are not allowed to delete user accounts.
</shiro:lacksPermission>
1
2
3
<p shiro:lacksPermission="user:create">
<a href="createUsers">Create a new User</a>
</p>
 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
总字数 163.8k 访客数 访问量