Shiro:由apache出品的简单的java安全框架。某些用途和Security相似。
UsernamePasswordToken Shiro用来封装用户登录信息,使用用户的登录信息创建Token。
SecurityManager Shiro的核心部分,负责安全认证和授权。
Suject Shiro的抽象概念,包含用户信息。
Realm 开发者自定义的模块,根据项目的需求,验证和授权的逻辑都写在Realm中。
AuthenticationInfo 用户的角色信息集合,认证时使用。
AuthorzationInfo 角色的权限信息集合,授权时使用。
DefaultWebSecurityManager 安全管理器,开发者自定义的Realm需要注入到DefaultWebSecurityManager进行管理才能生效。
ShiroFilterFactoryBean 过滤器工厂,使上面的组件得以运行。
用户在访问业务时,首先会被Shiro安全框架所拦截,用户通过UsernamePasswordToken拿到自己的Token,SecurityManager负责安全认证和授权,其中认证信息都在AuthenticationInfo 中获得,授权信息都在AuthorzationInfo 中获得。AuthenticationInfo 和AuthorzationInfo 中的逻辑信息都由Realm编写。最终用户根据Shiro判断的权限可以访问到不同的页面。
所需依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.woongcha</groupId> <artifactId>springboot-shiro</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-shiro</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.5.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1.tmp</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>application.xml
spring: datasource: driver-class-name: com.mysql.jdbc.Driver username: root password: 123456 url: jdbc:mysql://localhost:3306/myspringboot thymeleaf: prefix: classpath:/templates/ suffix: .html mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl详解认证逻辑的实现
ublic class AccountRealm extends AuthorizingRealm { private AccountService accountService; /** * 认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; Account account = accountService.findByUsername(token.getUsername()); if(account != null){ return new SimpleAuthenticationInfo(account,account.getPassword(),getName()); } return null; } } 客户端页面传递的username和password会直接封装到token里面,在调用token.getUsername()方法拿到用户名与数据库中的用户名做对比,如果为空,直接跳出验证;如果不为空,说明用户名在数据库当中,存在用户名为token中的用户。然后调用SimpleAuthenticationInfo函数,当前台传入的密码和account.getPassword()(数据库中的密码)比较,如果密码相等,就通过;如果不一样,抛出异常。
详解DefaultWebSecurityManager Realm ShiroFilterFactoryBean 逻辑的实现
@Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){ ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); factoryBean.setSecurityManager(securityManager); return factoryBean; } @Bean public DefaultWebSecurityManager securityManager(@Qualifier("accountRealm") AccountRealm accountRealm){ DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(accountRealm); return manager; } @Bean public AccountRealm accountRealm(){ return new AccountRealm(); } }利用Ioc容器原理,依次将AccountRealm DefaultWebSecurityManager ShiroFilterFactoryBean 注入容器并取出,使三个组件连通。
编写认证和授权规则
认证过滤器:
anon:无需认证
authc:必须认证
authcBasic:需要通过HttpBasic认证
user:不一定通过认证,只要曾经被Shiro记录过即可。
授权过滤器:
perms:必须拥有某个权限
role:必须拥有某个角色
port:请求的端口必须是指定值。
rest:请求必须基于Restful ,POST,GET,PUT,DELETE
ssl:必须是安全的URL请求,符合HTTPS协议。
创建三个页面分配赋予不同权限main.html,manage.html,administrator.html
访问权限如下:
1 必须登录才能访问 main
2 用户必须有manage权限 manage.html
3用户必须有administrator角色 administrator.html
在ShiroConfig中预设登录界面,不使用默认的login界面
@Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){ ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); factoryBean.setSecurityManager(securityManager); //权限设置 Map<String,String> map = new Hashtable<>(); map.put("/main","authc"); map.put("/manage","perms[manage]"); map.put("/administrator","roles[administrator]"); factoryBean.setFilterChainDefinitionMap(map); //设置登陆页面 factoryBean.setLoginUrl("/login"); return factoryBean; } @Bean public DefaultWebSecurityManager securityManager(@Qualifier("accountRealm") AccountRealm accountRealm){ DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(accountRealm); return manager; } @Bean public AccountRealm accountRealm(){ return new AccountRealm(); } }controller层
@Controller public class AccountController { @GetMapping("/{url}") public String redirect(@PathVariable("url") String url) { return url; } @PostMapping("/login") public String login(String username, String password, Model model) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { subject.login(token); return "index"; } catch (UnknownAccountException e) { e.printStackTrace(); model.addAttribute("msg", "用户名错误!"); return "login"; } catch (IncorrectCredentialsException e) { model.addAttribute("msg", "密码错误!"); e.printStackTrace(); return "login"; } }subject.login(token) 将验证的部分交给Shiro来做,通过subject的login方法,验证身份。
登陆页面
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="shortcut icon" href="#"/> </head> <body> <form action="/login" method="post"> <table> <span th:text="${msg}" style="color: red"></span> <tr> <td>用户名:</td> <td> <input type="text" name="username"/> </td> </tr> <tr> <td>密码:</td> <td> <input type="password" name="password"/> </td> </tr> <tr> <td> <input type="submit" value="登录"/> </td> </tr> </table> </form> </body> </html>总结:在Shiro Config文件中,通过依赖注入的方式将三个模块串联起来,只需编写ShiroFilterFactoryBean的业务逻辑即可,且在controller层中,将验证的部分交给Shiro来做,通过subject的login方法,验证身份,达到登陆验证的目的。