ShiroConfig
,配置SecuityManager
,其中SecurityManager
为shiro
的管理器,管理着所有的Subject
;ShiroConfig
配置 Shiro
的过滤工厂类 ShiroFilterFactoryBean
,用于过滤一些不需要的资源,如静态资源;其依赖于 SecurityManager
;doGetAuthorizationInfo()
和 认证 doGetAuthenticationInfo()
ok,开始实现,技术选型:springboot + h2数据库 + shiro + mybatis-plus
1、先引入依赖:
<!--thymeleaf-->
<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>
<!-- shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.2.0</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--h2-->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
2、建立数据库:在 resources 目录下新建一个包db,导入schema-h2.sql
和data-h2.sql
这两个文件,
schema-h2.sql
:
DROP TABLE IF EXISTS user;
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
username VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
password VARCHAR(128) NULL DEFAULT NULL COMMENT '密码',
create_time DATE NULL DEFAULT NULL COMMENT '创建时间',
status CHAR(2) NULL DEFAULT NULL COMMENT '状态',
PRIMARY KEY (id)
);
data-h2.sql
:
DELETE FROM user;
INSERT INTO user (id, username, password, create_time, status) VALUES
(1, 'Jone', 'e10adc3949ba59abbe56e057f20f883e', null, '1'),
(2, 'Jack', 'e10adc3949ba59abbe56e057f20f883e', null, '1'),
(3, 'Tom', 'e10adc3949ba59abbe56e057f20f883e', null, '1'),
(4, 'Sandy', 'e10adc3949ba59abbe56e057f20f883e', null, '0'),
(5, 'Billie', 'e10adc3949ba59abbe56e057f20f883e', null, '0');
3、建立和数据库对应的实体类:
@Data
public class User implements Serializable {
private Long id;
@TableField("username")
private String userName;
@TableField("password")
private String password;
@TableField("create_time")
private Date createTime;
@TableField("status")
private String status;
}
4、建立 dao层 和 service层
dao层:
public interface UserMapper extends BaseMapper<User> {
}
service层:
接口:
public interface UserService extends IService<User> {
User findUserByUserName(String userName);
}
--------------------------------------------------------------
实现类:
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public User findUserById(Long id) {
return baseMapper.selectById(id);
}
@Override
public User findUserByUserName(String userName) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper();
queryWrapper.eq(User::getUserName, userName);
return baseMapper.selectOne(queryWrapper);
}
}
5、编写 ShiroConfig 和 自定义Realm
ShiroConfig :
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
//设置登录的url
shiroFilterFactoryBean.setLoginUrl("/login");
//设置未授权的url
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
LinkedHashMap<String, String> filterChainMap = new LinkedHashMap<>();
//定义filterChain,设置静态资源不拦截
filterChainMap.put("/css/**", "anon");
filterChainMap.put("/js/**", "anon");
filterChainMap.put("/fonts/**", "anon");
filterChainMap.put("/img/**", "anon");
//配置退出过滤器,shiro已帮我们实现了
filterChainMap.put("/logout", "logout");
filterChainMap.put("/", "anon");
//除以上url,其他url都必须通过认证,未认证默认自动访问loginUrl
filterChainMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
return securityManager;
}
@Bean
public ShiroRealm shiroRealm() {
ShiroRealm shiroRealm = new ShiroRealm();
return shiroRealm;
}
}
自定义Realm:
@Slf4j
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 授权,获取用户角色和权限
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 登录认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取用户输入的用户名和密码
String username = (String)authenticationToken.getPrincipal();
String password = new String((char[]) authenticationToken.getCredentials()); //特别注意这里转String要这样写
log.info("用户:{}, 密码:{}", username, password);
//查询用户信息
User user = userService.findUserByUserName(username);
if (user == null) {
throw new UnknownAccountException("用户名或密码错误");
}
if (!password.equals(user.getPassword())) {
throw new IncorrectCredentialsException("用户名或密码错误");
}
if (user.getStatus().equals("0")) {
throw new LockedAccountException("账号状态异常,请联系管理员!");
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, password, getName());
return authenticationInfo;
}
}
6、编写 controller 层
@Slf4j(topic = "UserController")
@Controller
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/login")
public String login() {
return "login";
}
@PostMapping("/login")
@ResponseBody
public ServerResponse login(@RequestParam("username") String userName, @RequestParam("password") String password) {
//md5加密
password = SecureUtil.md5(password);
log.info("加密后的密码:{}", password);
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
//获取Subject对象
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return ServerResponse.createBySuccessMessage("登录成功");
} catch (AuthenticationException e) {
return ServerResponse.createByErrorMessage("登录失败");
}
}
}
7、编写一个极其简陋的登录页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="/login" method="post">
<input type="text" placeholder="用户名" name="username"><br>
<input type="password" placeholder="密码" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
8、开始测试,启动项目,访问http://localhost:8080/login
输入用户名Jone 和 密码123456
测试结果:
整合认证ok
spring:
#h2内存数据库
datasource:
driver-class-name: org.h2.Driver
schema: classpath:db/schema-h2.sql
data: classpath:db/data-h2.sql
url: jdbc:h2:mem:test
username: root
password: test