Shiro

Shiro

QuickStart

  • 主要方法(Spring Security都有)
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
currentUser.isAuthenticated()
currentUser.getPrincipal()
currentUser.hasRole("schwartz")
currentUser.isPermitted("lightsaber:wield")
currentUser.logout();

SpringBoot-Shiro

环境搭建

添加依赖

<!--
    Subject: 用户
    SecurityManager: 管理所有用户
    Realm: 连接数据库
 -->
<!-- SpringBoot-Shiro -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.1</version>
</dependency>

编写页面跳转路由Controller

  • 涉及的页面就简单写一下就行

    • templates/index.html

    • templates/user/add.html

    • templates/user/update.html

@Controller
public class MyController {
    @RequestMapping({"/", "/index"})
    public String toIndex(Model model) {
        model.addAttribute("msg", "helloShiro");
        return "index";
    }

    @RequestMapping("/add")
    public String toAdd() {
        return "user/add";
    }

    @RequestMapping("/update")
    public String toUpdate() {
        return "user/update";
    }
}

编写自定义的Realm

// 自定义 realm
public class UserRealm extends AuthorizingRealm {
    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了授权");
        return null;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行了认证");
        return null;
    }
}

在SpringBoot中配置Shiro

@Configuration
public class ShiroConfig {
    // Step 3: ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager manager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        // 设置安全管理器
        bean.setSecurityManager(manager);
        return bean;
    }

    // Step 2: DefaultWebSecurityManager
    // @Qualifier("userRealm") 中间参数为变量名, 可以锁定一个参数
    @Bean(name = "defaultWebSecurityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        // 关联 userRealm
        manager.setRealm(userRealm);
        return manager;
    }

    // Step 1: 创建 realm 对象, 需要自定义
    @Bean(name = "userRealm")
    public UserRealm userRealm() {
        return new UserRealm();
    }
}

登录拦截

在ShiroFilterFactoryBean中添加过滤器

@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager manager) {
    ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
    // 设置安全管理器
    bean.setSecurityManager(manager);

    // 添加Shiro的内置过滤器
    /*
        anon: 无需认证即可访问
        authc: 必须认证才能访问
        user: 必须拥有 记住我 功能才能用
        perms: 拥有对某个资源的权限才能访问
        role: 拥有某个角色权限才能访问
     */
    Map<String, String> filterMap = new LinkedHashMap<>();
  	// add 不用登录
    filterMap.put("/user/add", "anon"); // anon 别写错了
  	// update 需要登录
    filterMap.put("/user/update", "authc");
  	// filterMap.put("/user/*", "authc"); 通配符也支持
    bean.setFilterChainDefinitionMap(filterMap);
  	// 未登录情况下, 需要登录跳转的页面链接
    bean.setLoginUrl("/toLogin");

    return bean;
}

用户认证

Controller添加login方法

  • username与password由前端传递
@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); // 执行登录方法, 如果没异常就OK了
    } catch (UnknownAccountException e) { // 用户名不存在
        model.addAttribute("msg", "用户名错误");
        return "login";
    } catch (IncorrectCredentialsException e) { // 密码错误
        model.addAttribute("msg", "密码错误");
    }
    return "index";
}

在UserRealm中编写认证方法

// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    System.out.println("执行了认证");

    // 用户名, 密码
    String name = "d";
    String pwd = "1";

    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

    if (!token.getUsername().equals(name)) {
        return null; // 抛出异常, UnknownAccountException
    }

    // 密码认证, Shiro完成
    return new SimpleAuthenticationInfo("", pwd, "");
}

整合Mybatis

添加依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

在application.yaml中配置JDBC、Mybatis和Druid

spring:
    datasource:
        username: root
        password: lu123
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mybatis?autoReconnect=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
        type: com.alibaba.druid.pool.DruidDataSource

        initialSize: 5
        minIdle: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true

        filters: stat,wall,log4j
        maxPoolPreparedStatementPerConnectionSize: 20
        useGlobalDataSourceStat: true
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

mybatis:
    type-aliases-package: com.lu.pojo
    mapper-locations: classpath:mapper/*.xml

编写用户类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
}

在认证过程中使用数据库查找到的数据

// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    System.out.println("执行了认证");

    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

    // 连接真实数据库
    User user = service.findByName(token.getUsername());

    if (user == null) {
        return null; // 抛出异常, UnknownAccountException
    }

    // 密码认证, Shiro完成
    return new SimpleAuthenticationInfo("", user.getPwd(), "");
}

用户授权

更改User实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
    private String perms; // 权限
}

在ShiroFilterFactoryBean方法中添加请求的访问权限

@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager manager) {
    ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
    // 设置安全管理器
    bean.setSecurityManager(manager);

    // 添加Shiro的内置过滤器
    /*
        anon: 无需认证即可访问
        authc: 必须认证才能访问
        user: 必须拥有 记住我 功能才能用
        perms: 拥有对某个资源的权限才能访问
        role: 拥有某个角色权限才能访问
     */
    // 过滤器放进去的键值对有顺序, 先处理子请求再处理父请求
    Map<String, String> filterMap = new LinkedHashMap<>();
    // 权限, 正常情况下未授权会转发到未授权页面
    filterMap.put("/user/add", "perms[user:add]");
    filterMap.put("/user/update", "perms[user:update]");
    // 拦截
    filterMap.put("/user/*", "authc");
    bean.setFilterChainDefinitionMap(filterMap);

    bean.setLoginUrl("/toLogin");
    // 未授权页面
    bean.setUnauthorizedUrl("/unauth");
    return bean;
}

编写未授权的跳转页面

@RequestMapping("/unauth")
@ResponseBody
public String unauth() {
    return "Unauthorized User!!!!";
}

增添授权操作,通过数据库获取权限

// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("执行了授权");

    // SimpleAuthorizationInfo
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    // 拿到当前登录的对象
    Subject subject = SecurityUtils.getSubject();
    User cur_user = (User) subject.getPrincipal();
    // 设置当前用户权限
    info.addStringPermission(cur_user.getPerms());

    return info;
}

整合Thymeleaf

添加依赖

<!--Shiro-Thymeleaf整合-->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>

添加命名空间头文件

xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"

在类ShiroConfig中注册Bean

// 整合ShiroDialect: 用于整合Shiro和Thymeleaf
@Bean
public ShiroDialect getShiroDialect() {
    return new ShiroDialect();
}

修改index.html页面逻辑

  • 有什么权限显示什么链接
  • 登录后不显示登录页面
<body>
    <h1>首页</h1>

    <div th:text="${msg}" style="color: red"></div>
    <br>
    <div shiro:hasPermission="user:add">
        <a th:href="@{/user/add}">Add</a>
    </div>
    <br>
    <div shiro:hasPermission="user:update">
        <a th:href="@{/user/update}">Update</a>
    </div>
    <br>
    <div shiro:notAuthenticated>
        <a th:href="@{/toLogin}">登录</a>
    </div>
<!--    <a th:href="@{/toLogin}">Login</a>-->
</body>
posted @   James-Allen  阅读(19)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
点击右上角即可分享
微信分享提示