1. Shiro的核心组件
- UsernamePasswordToken:用来封装用户的登录信息,通过登录信息创建令牌Token;登陆的过程即Shiro验证令牌是否具有合法身份以及相关权限
- SecurityManager:Shiro的核心,负责安全认证和授权
- Subject:Shiro的一个抽象概念,包含了用户信息
- Realm:开发者自定义的模块,根据项目的需求,验证和授权的逻辑在Realm中实现
- AuthenticationInfo:用户的角色信息集合,认证的时候使用
- AuthorizationInfo:角色的权限信息集合,授权时使用
- DefaultWebSecurityManager:安全管理器,用户自定义的Realm需要注入安全管理器才能使用
- ShiroFilterFactoryBean:过滤器工厂,Shiro的基本运行机制是开发者定义规则,shiro去执行;具体的执行操作就是有ShiroFilterFactory创建一个个Filter对象来完成
2. 使用步骤
2.1 引入依赖
<!-- 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>8.0.20</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
2.2 创建数据表 account,添加两条记录
CREATE TABLE `account` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(20) DEFAULT NULL,
`password` varchar(20) DEFAULT NULL,
`perms` varchar(20) DEFAULT NULL,
`role` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
LOCK TABLES `account` WRITE;
INSERT INTO `account` VALUES (1,'zs','123123','',''),(2,'ls','123123','manage',''),(3,'ww','123123','manage','administrator');
UNLOCK TABLES;
2.3 Java代码准备
创建实体类Account
@Data
public class Account {
private Integer id;
private String username;
private String password;
private String perms;
private String role;
}
创建AccountMapper接口
public interface AccountMapper extends BaseMapper<Account> {
}
创建application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Drive
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
启动类添加注解扫描Mapper接口
@SpringBootApplication
@MapperScan("com.southwind.springbootshirodemo.mapper")
public class SpringbootshirodemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootshirodemoApplication.class, args);
}
}
首先通过单元测试调试 AccoutMapper 接口,能够调通之后,编写service层代码
@SpringBootTest
class AccountMapperTest {
@Autowired
private AccountMapper accountMapper;
@Test
void test(){
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("username","user");
Account account = accountMapper.selectOne(wrapper);
System.out.println(account);
}
}
service层代码
public interface AccountService {
public Account findByUsername(String username);
}
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
public Account findByUsername(String username) {
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("username",username);
return accountMapper.selectOne(wrapper);
}
}
2.4 配置shiro,重点开始了!!!
回到shiro,完成自己的realm的编写
public class MyRealm extends AuthorizingRealm {
@Autowired
private AccountService accountService;
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 1. 获取令牌
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 2. 从令牌中获取用户信息
Account account = accountService.findByUsername(token.getUsername());
// 3. 返回用户角色信息集合
if(account != null){
return new SimpleAuthenticationInfo(account,account.getPassword(),getName());
}
return null;
}
}
// 先根据 username 进行查询,如果返回 null,则表示用户名错误,直接 return null 即可,Shiro 会自动抛出 UnknownAccountException 异常。
// 如果返回不为 null,则表示用户名正确,再验证密码,直接返回 SimpleAuthenticationInfo 对象即可,这里面封装了用户的角色信息集合
自定义MyRealm后,需要配置到安全管理器和shiro的过滤器工厂中
Realm -》 DefaultWebSecurityManager -》 ShiroFilterFactory
@Configuration
public class ShiroConfig {
@Bean
public MyRealm myRealm(){
return new MyRealm();
}
@Bean
public DefaultWebSecurityManager manager(@Qualifier("myRealm") MyRealm myRealm){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(myRealm);
return manager;
}
@Bean
public ShiroFilterFactoryBean filterFactoryBean(@Qualifier("manager") DefaultWebSecurityManager manager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(manager);
return factoryBean;
}
}
ShiroFilterFactory是Shiro自带的一个Filter工厂实例,所有的认证和授权的操作都是在这里完成的,开发者只需要定义规则,具体执行由Shiro完成
给ShiroFilterFactory设置规则
认证过滤器:
anon:无需认证即可访问,游客身份。
authc:必须认证(登录)才能访问。
authcBasic:需要通过 httpBasic 认证。
user:不一定已通过认证,只要是曾经被 Shiro 记住过登录状态的用户就可以正常发起请求,比如 rememberMe。
授权过滤器:
perms:必须拥有对某个资源的访问权限(授权)才能访问。
role:必须拥有某个角色权限才能访问。
port:请求的端口必须为指定值才可以访问。
rest:请求必须是 RESTful,method 为 post、get、delete、put。
ssl:必须是安全的 URL 请求,协议为 HTTPS。
比如,我们创建三个页面,main.html、manage.html、administrator.html,要求如下:
1、必须是登录状态才可以访问 main.html。
2、用户必须拥有 manage 授权才可以访问 manage.html。
3、用户必须拥有 administrator 角色才能访问 administrator.html。
那么我们可以给ShiroFilterManager配置成为下面的方式
@Bean
public ShiroFilterFactoryBean filterFactoryBean(@Qualifier("manager") DefaultWebSecurityManager manager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(manager);
Map<String,String> map = new HashMap<>();
map.put("/main","authc");
map.put("/manage","perms[manage]");
map.put("/administrator","roles[administrator]");
factoryBean.setFilterChainDefinitionMap(map);
//设置登录页面
factoryBean.setLoginUrl("/login");
//未授权页面
factoryBean.setUnauthorizedUrl("/unauth");
return factoryBean;
}
对应的Controller如图所示
@Controlle
public class MyController {
@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) {
model.addAttribute("msg","用户名错误");
return "login";
} catch (IncorrectCredentialsException e) {
model.addAttribute("msg", "密码错误");
return "login";
}
}
@RequestMapping("/unauth")
@ResponseBody
public String unauth(){
return "未授权没有访问权限";
}
}
2.5 那么现在只需要登录就可以访问main.html
但是无法访问manager.html,这是因为没有授权,接下来我们完成授权的操作,回到MyRealm中
修改doGetAuthorizationInfo方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取当前登录对象
Subject subject = SecurityUtils.getSubject();
Account account = (Account) subject.getPrincipal();
//设置角色
Set<String> roles = new HashSet<>();
roles.add(account.getRole());
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
//设置权限
info.addStringPermission(account.getPerms());
return info;
}
数据库数据如图所示
zs 没有权限和角色,所以登录之后只能访问 main.html。
ls 拥有 manage 权限,没有角色,所以登录之后可以访问 main.html、manage.html。
ww 拥有 manage 权限和 administrator 角色,所以登录之后可以访问 main.html、manage.html、administrator.html。