Shiro与SpringBoot整合
一、框架整合
1、创建项目添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.9.0</version>
</dependency>
<!-- mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
2、添加配置文件application.yml
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*.xml
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/shirodb?characterEncoding=utf-8&useSSL=false
username: root
password: 508735398
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
shiro:
loginUrl: /myController/login
3、启动类
@SpringBootApplication
@MapperScan("com.wk.shiro.mapper")
public class ShiroSpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(ShiroSpringbootApplication.class, args);
}
}
二、登录认证案例
1、创建数据库及接口服务
- 数据库
CREATE DATABASE IF NOT EXISTS `shirodb` CHARACTER SET utf8mb4;
USE `shirodb`;
CREATE TABLE `user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` VARCHAR(30) DEFAULT NULL COMMENT '用户名',
`pwd` VARCHAR(50) DEFAULT NULL COMMENT '密码',
`rid` BIGINT(20) DEFAULT NULL COMMENT '角色编号',
PRIMARY KEY (`id`)) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='用户表
- User实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private String pwd;
private Integer rid;
}
- 新建UserMapper接口
@Repository
public interface UserMapper extends BaseMapper<User> {
}
- 新建UserService接口
public interface UserService {
//用户登录
User getUserByName(String name);
}
- 新建UserServiceImpl实现类
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User getUserByName(String name) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name",name);
User user = userMapper.selectOne(wrapper);
return user;
}
}
2、实现登录认证
- 1、新建MyRealm
@Component
public class MyRealm extends AuthorizingRealm {
@Autowired
protected UserService userService;
//自定义授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
//自定义登录认证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1、获取用户身份信息
String name = token.getPrincipal().toString();
//2、调用业务层获取用户信息(数据库)
User userByName = userService.getUserByName(name);
//3、非空判断,将数据完成封装返回
if (userByName!=null){
AuthenticationInfo info = new SimpleAuthenticationInfo(
//1、身份信息
token.getPrincipal(),
//2、获取密码信息
userByName.getPwd(),
ByteSource.Util.bytes("salt"),
//指定对应用户信息
token.getPrincipal().toString()
);
return info;
}
return null;
}
}
- 2、新建MyController
@Controller
@RequestMapping("myController")
public class MyController {
//跳转页面
@GetMapping("login")
public String login(){
return "Login";
}
@GetMapping("userLogin")
public String userLogin(String name, String pwd, HttpSession session){
//1、获取Subject对象
Subject subject = SecurityUtils.getSubject();
//2、封装请求数据到token
AuthenticationToken token =new UsernamePasswordToken(name,pwd);
//3、调用login方法进行登录认证
try {
subject.login(token);
//return "登录成功";
session.setAttribute("user",token.getCredentials().toString());
return "Main";
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("登录失败");
return "登录失败";
}
}
}
- 3、新建ShiroConfig
@Configuration
public class ShiroConfig {
@Autowired
private MyRealm myRealm;
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
//1、创建defaultWebSecurityManager对象
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//2、创建加密对象设置相关属性
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//采用MD5加密,采用迭代次数
matcher.setHashAlgorithmName("md5");
matcher.setHashIterations(3);
//3、将加密对象存储到MyRealm中
myRealm.setCredentialsMatcher(matcher);
//4、将MyRealm存入到defaultWebSecurityManager对象中
defaultWebSecurityManager.setRealm(myRealm);
//5、返回
return defaultWebSecurityManager;
}
@Bean
public DefaultShiroFilterChainDefinition
shiroFilterChainDefinition(){
DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
//设置不认证可以访问的资源
definition.addPathDefinition("/myController/userLogin","anon");
definition.addPathDefinition("/myController/Login","anon");
definition.addPathDefinition("/login","anon");
//设置需要进行登录认证的拦截范围
definition.addPathDefinition("/**","authc");
return definition;
}
}
- 新建一个简单的Login页面
<!DOCTYPE html> <html lang="en"> <head>
<meta charset="UTF-8">
<title>Title</title>
</head> <body>
<h1>Shiro 登录认证</h1>
<br>
<form action="/myController/userLogin">
<div>用户名:<input type="text" name="name" value=""></div>
<div>密码:<input type="password" name="pwd" value=""></div>
<div><input type="submit" value="登录"></div>
</form>
</body>
</html>
- 新建一个Main页面
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head>
<meta charset="UTF-8">
<title>Title</title>
</head> <body>
<h1>Shiro 登录认证后主页面</h1>
<br>
登录用户为:<span th:text="${session.user}"></span>
</body>
3、多个 realm 的认证策略设置
当应用程序配置多个 Realm 时,例如:用户名密码校验、手机号验证码校验等等。Shiro 的 ModularRealmAuthenticator 会使用内部的AuthenticationStrategy 组件判断认证是成功还是失败。
AuthenticationStrategy 是一个无状态的组件,它在身份验证尝试中被询问4 次(这4 次交互所需的任何必要的状态将被作为方法参数):
- 在所有 Realm 被调用之前
- 在调用 Realm 的 getAuthenticationInfo 方法之前
- 在调用 Realm 的 getAuthenticationInfo 方法之后
- 在所有 Realm 被调用之后
认证策略的另外一项工作就是聚合所有 Realm 的结果信息封装至一个AuthenticationInfo 实例中,并将此信息返回,以此作为 Subject 的身份信息。
AuthenticationStrategy class | 描述 |
---|---|
AtLeastOneSuccessfulStrategy | 只要有一个(或更多)的 Realm 验证成功,那么认证将视为成功 |
FirstSuccessfulStrategy | 第一个 Realm 验证成功,整体认证将视为成功,且后续 Realm 将被忽略 |
AllSuccessfulStrategy | 所有 Realm 成功,认证才视为成功 |
ModularRealmAuthenticator 内置的认证策略默认实现是AtLeastOneSuccessfulStrategy 方式。可以通过配置修改策略
4、remember me 功能
步骤
- 1、首先在登录页面选中 RememberMe 然后登录成功;如果是浏览器登录,一般会把 RememberMe 的 Cookie 写到客户端并保存下来;
- 2、关闭浏览器再重新打开;会发现浏览器还是记住你的;
- 3、访问一般的网页服务器端,仍然知道你是谁,且能正常访问;
- 4、但是,如果我们访问电商平台时,如果要查看我的订单或进行支付时,此时还是需要再进行身份认证的,以确保当前用户还是你。