SpringBoot整合shiro

Shiro权限框架入门
本文介绍Apache Shiro权限框架的基础知识及其实现过程,包括核心组件的定义与使用,通过Spring Boot快速搭建Shiro环境,并实现用户认证功能。

shiro介绍

官网: Apache Shiro | Simple. Java. Security.

Shiro 是 Java 的一个安全(权限)框架。

shiro功能

Shiro 可以完成:认证、授权、加密、会话管理、与Web 集成、缓存等。

  • Authentication: 身份认证/登录,验证用户是不是拥有相应的身份

  • Authorization: 授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限

        ...... (其他功能略,自己百度)

shiro核心组件

下述为实现shiro较为核心的几个组件

 ShiroFilterFactory是整个Shiro的入口点

简而言之: 

  • 对于Realms,我们只需要继承 抽象类 AuthorizingRealm 即可发现,我们需要重写两个方法,分别是用于实现认证功能的 doGetAuthenticationInfo方法 和用于实现授权的doGetAuthorizationInfo方法。

  • SecurityManager属于一个中间安全管理器,通过它可实现各种安全服务

  • ShiroFilterFactoryBean则属于一个过滤器工厂,我们可以在这里定义哪些接口需要认证和授权

而对于shiro的快速入门,其实就是对以上的,SecurityManager、Realms、ShiroFilterFactoryBean三个类的重写与实现。

使用

        我这个只演示认证功能的实现,其他功能,如授权,需要你自行百度。

        创建一个普通的SpringBoot工程,只需要选择web依赖即可。

1、引入依赖

 <!--shiro-->
 <dependency>
     <groupId>org.apache.shiro</groupId>
     <artifactId>shiro-spring-boot-web-starter</artifactId>
     <version>1.4.0</version>
 </dependency>
 <!--lombok-->
 <dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
 </dependency>

2、编写用于测试的基本接口

        VO类,用于接收用户名和密码

 @Data
 public class UserLoginVo {
     private String userName;
     private String passWord;
 }

        登录接口和另一个普通访问的接口

 @RestController
 public class UserController {
     
     /**
      * 登录
      *
      * @param loginVo 用户名和密码
      * @return {@link String}
      */
     @PostMapping("/login")
     public String login(@RequestBody UserLoginVo loginVo) {
         System.out.println(loginVo);
         return "登录成功";
     }
 ​
     @GetMapping("/hello")
     public String hello() {
         System.out.println("hello");
         return "打招呼";
     }
 ​
 }

        用接口测试两个接口,都可使用

3、实现shiro核心组件

Realms组件的实现

 @Component
 public class AccountRealm extends AuthorizingRealm {
 ​
     // 授权
     @Override
     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
         System.out.println("授权");
         return null;
     }
 ​
     // 认证
     @Override
     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
         System.out.println("认证");
         // 获取登录的用户名
         String username = (String) token.getPrincipal();
         // 判断改用户是否存在
         if (!"hsx".equals(username)) {
             throw new UnknownAccountException("账户不存在!");
         }
         // 验证密码
         return new SimpleAuthenticationInfo(username, "123", getName());
     }
 }

        在此处,我账号和密码分别写死为 "hsx" 和 "123"。在实际开发中,这部分的账号和密码的验证,应该是来着查询数据库的。 

ShiroFilterFactoryBean和SecurityManager组件的实现

        在这里,我直接演示使用注解的方法来实现安全管理。

 @Configuration
 public class ShiroConfig {
     //DefaultWebSecurityManager 安全管理器
     @Bean
     public DefaultWebSecurityManager securityManager(AccountRealm accountRealm) {
         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
         //关联 我们自定义的AccountRealm
         securityManager.setRealm(accountRealm);
         return securityManager;
     }
 ​
     //shiroFilterFactoryBean
     @Bean
     public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
         ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
         //设置安全管理器
         bean.setSecurityManager(securityManager);
         return bean;
     }
     
     //开启注解代理(默认好像已经开启,可以不要)
     @Bean
     public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
         AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
         authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
         return authorizationAttributeSourceAdvisor;
     }
 ​
     @Bean
     public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
         DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
         creator.setProxyTargetClass(true);
         return creator;
     }    
     
 }

4、测试

        在配置了shiro的三个核心组件,SecurityManager、Realms、ShiroFilterFactoryBean。而且,重写了Realms中的认证方法。那我们就可以按照我们定义的规则进行认证了。

        只要我们在Controller接口上加上@RequiresAuthentication注解,那么该接口就需要认证之后才能访问。

 @GetMapping("/hello")
 @RequiresAuthentication
 public String hello() {
     System.out.println("hello");
     return "打招呼";
 }

        重启服务,调用 'hello' 接口。然后发现,系统报错,提示我们权限不足。无法访问该接口。

        若要访问改接口,那么我们需要进行认证

        在shiro中,只有调用SecurityUtils.getSubject()中的login,才可反问我们上面定义的doGetAuthenticationInfo方法。 

        所以,我们在login接口调用我们上述所说的方法,将前端传过来的账号和密码都传进去

 /**
  * 登录
  *
  * @param loginVo 用户名和密码
  * @return {@link String}
  */
 @PostMapping("/login")
 public String login(@RequestBody UserLoginVo loginVo) {
     SecurityUtils.getSubject().login(
        new UsernamePasswordToken(loginVo.getUserName(), loginVo.getPassWord()));
     System.out.println(loginVo);
     return "登录成功";
 }

        然后通过接口测试工具来调用/login接口,通过debug发现,的确进入了我们所定义的方法

        此时,我们再次调用/hello接口,然后可以发现,可以返回我们需要的数据

5、优化

        在上述,当未认证时,访问接口,前端则会报 500 错误。此时,我们可以定义个全局异常捕获,让报错更加的优雅。

 package com.hsx.utils;
 ​
 import org.apache.shiro.authz.AuthorizationException;
 import org.apache.shiro.authz.UnauthenticatedException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
 ​
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 ​
 ​
 @RestControllerAdvice
 public class BaseExceptionHandler {
 ​
     /**
      * 处理未授权的异常
      */
     @ExceptionHandler(value = AuthorizationException.class)
     public String returnErrorMsg(HttpServletRequest request, HttpServletResponse response, AuthorizationException exception) {
         return "您没有权限";
     }
 ​
     /**
      * 处理未登录的异常
      */
     @ExceptionHandler(value = UnauthenticatedException.class)
     public String returnUnLoginErrorMsg(HttpServletRequest request, HttpServletResponse response, UnauthenticatedException exception) {
 ​
         return "您还未登录";
     }
 ​
 ​
     /**
      * 处理运行时异常
      */
     @ExceptionHandler(value = Exception.class)
     public String returnRunningException(Exception e) {
         System.out.println(e.getMessage());
         return "系统错误";
     }
 ​
 }

        此时,再次访问没有权限的接口。

总结

        上述代码,可以先根据我的步骤来实现,然后对比oj系统中相应部分的实现。其实原理都是一样的。不一样的地方,比如在认证方法doGetAuthenticationInfo那里,我为了方便,就是将账号密码写死,而在oj系统中,是进行了JWT反解析获取到用户ID,然后去数据库查询用户是否存在,最后才进行密码的比较。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值