shiro介绍
官网: Apache Shiro | Simple. Java. Security.
Shiro 是 Java 的一个安全(权限)框架。
shiro功能
Shiro 可以完成:认证、授权、加密、会话管理、与Web 集成、缓存等。
????????...... (其他功能略,自己百度)
shiro核心组件
下述为实现shiro较为核心的几个组件

?ShiroFilterFactory是整个Shiro的入口点
简而言之:?
而对于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,然后去数据库查询用户是否存在,最后才进行密码的比较。

|