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方法,验证身份。
登陆页面
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<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方法,验证身份,达到登陆验证的目的。