SpringBoot框架整合Shiro框架
最近在学习Shiro框架,做个笔记记录一下
修改pom.xml
<?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.7.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.young</groupId>
<artifactId>shiro03</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shiro03</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-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<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.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</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.5.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--引入Shiro和ehcache-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.3</version>
</dependency>
<!--thymeleaf和shiro-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.1.0</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
目录如下:
ShiroConfiguration.java是配置类
@Configuration
public class ShiroConfiguration {
//获取过滤工厂
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean filterFactoryBean=new ShiroFilterFactoryBean();
filterFactoryBean.setSecurityManager(defaultWebSecurityManager);
Map<String,String>map=new HashMap<>();
map.put("/index","authc");
map.put("/login","anon");
map.put("/register","anon");
filterFactoryBean.setLoginUrl("/login");
filterFactoryBean.setFilterChainDefinitionMap(map);
return filterFactoryBean;
}
//获取安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager=new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
@Bean
public Realm getRealm(){
CustomerRealm customerRealm=new CustomerRealm();
//设置加密管理器
HashedCredentialsMatcher hashedCredentialsMatcher=new HashedCredentialsMatcher();
//设置散列次数
hashedCredentialsMatcher.setHashIterations(ShiroConstant.SHIRO_ITERATORS);
//设置加密算法
hashedCredentialsMatcher.setHashAlgorithmName(ShiroConstant.SHIRO_ALGORITHM);
customerRealm.setCredentialsMatcher(hashedCredentialsMatcher);
//设置缓存管理器
customerRealm.setCacheManager(new EhCacheManager());
//开启全局缓存
customerRealm.setCachingEnabled(true);
customerRealm.setAuthenticationCachingEnabled(true);
customerRealm.setAuthenticationCacheName("authenticationCache");
customerRealm.setAuthorizationCachingEnabled(true);
customerRealm.setAuthorizationCacheName("authorizationCache");
return customerRealm;
}
//解决Shiro标签在thymeleaf失效的问题
@Bean(name="shiroDialect")
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
ShiroConstant是常量类,配置一些常量
public class ShiroConstant {
//散列次数
public static Integer SHIRO_ITERATORS=1024;
//随机盐长度
public static Integer SHIRO_LENGTH=8;
//散列算法
public static String SHIRO_ALGORITHM="MD5";
}
User实体类
public class User {
private Integer id;
private String username;
private String password;
private String salt;
private String role;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
@Override
public String toString(){
return "User{id="+id+",username="+username+",password="+password+
",salt="+salt+",role="+role+"}";
}
}
UserMapper接口:
@Mapper
public interface UserMapper {
int insert(User user);
User getUserByUsername(String userName);
}
UserService接口
public interface UserService {
int insert(User user);
User getUserByUsername(String username);
}
UserServiceImpl实现类
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public int insert(User user) {
String salt= SaltUtil.getSalt(ShiroConstant.SHIRO_LENGTH);
user.setSalt(salt);
Md5Hash md5Hash=new Md5Hash(user.getPassword(),user.getSalt(),ShiroConstant.SHIRO_ITERATORS);
String password=md5Hash.toHex();
user.setPassword(password);
user.setRole("user");
return userMapper.insert(user);
}
@Override
public User getUserByUsername(String username) {
return userMapper.getUserByUsername(username);
}
}
ApplicationContextUtil工具类
@Component
public class ApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context=applicationContext;
}
public static Object getBean(String beanName){
return context.getBean(beanName);
}
}
CustomerByteSource工具类,用于获取ByteSource
public class CustomerByteSource implements ByteSource {
private byte[]bytes;
public CustomerByteSource(byte[]bytes){
this.bytes=bytes;
}
public CustomerByteSource(char[]chars){
this.bytes= CodecSupport.toBytes(chars);
}
public CustomerByteSource(String string){
this.bytes=CodecSupport.toBytes(string);
}
public CustomerByteSource(ByteSource byteSource){
this.bytes=byteSource.getBytes();
}
@Override
public byte[] getBytes() {
return this.bytes;
}
@Override
public String toHex() {
return Hex.encodeToString(bytes);
}
@Override
public String toBase64() {
return Base64.encodeToString(bytes);
}
public String toString(){
return this.toBase64();
}
@Override
public boolean isEmpty() {
return bytes==null||bytes.length==0;
}
}
SaltUtil工具类,获取随机盐
//获取随机盐的工具类
public class SaltUtil {
public static String getSalt(int n){
char[]chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()".toCharArray();
StringBuilder sb=new StringBuilder();
for(int i=0;i<n;i++){
char aChar=chars[new Random().nextInt(chars.length)];
sb.append(aChar);
}
return sb.toString();
}
}
CustomerRealm,自定义的Realm
public class CustomerRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取主体
String principal=(String)principalCollection.getPrimaryPrincipal();
UserService userService=(UserService) ApplicationContextUtil.getBean("userService");
User user=userService.getUserByUsername(principal);
if(!ObjectUtils.isEmpty(user)){
SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
authorizationInfo.addRole(user.getRole());
return authorizationInfo;
}
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取主体
String principal=(String)authenticationToken.getPrincipal();
//由于CustomerRealm未交由工程管理
UserService userService=(UserService) ApplicationContextUtil.getBean("userService");
User user=userService.getUserByUsername(principal);
if(!ObjectUtils.isEmpty(user)){
return new SimpleAuthenticationInfo(principal,user.getPassword(),new CustomerByteSource(user.getSalt()),this.getName());
}
return null;
}
}
LoginController
@Controller
public class LoginController {
@RequestMapping("index")
public String index(){
return "index";
}
@RequestMapping("login")
public String login(){
return "login";
}
@RequestMapping("register")
public String register(){
return "register";
}
}
UserController
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
//登录
@RequestMapping("login")
public ModelAndView login(User user){
ModelAndView modelAndView=new ModelAndView();
try{
//获取系统主题
Subject subject= SecurityUtils.getSubject();
//设置令牌
UsernamePasswordToken token=new UsernamePasswordToken(user.getUsername(),user.getPassword());
subject.login(token);
modelAndView.setViewName("redirect:/index");
}catch (UnknownAccountException e){
System.out.println("用户名错误");
modelAndView.setViewName("redirect:/login");
}catch (IncorrectCredentialsException e){
System.out.println("密码错误");
modelAndView.setViewName(("redirect:/login"));
}catch (Exception e){
modelAndView.setViewName("redirect:/login");
}
return modelAndView;
}
//退出登录
@RequestMapping("logout")
public ModelAndView logout(){
Subject subject=SecurityUtils.getSubject();
subject.logout();
ModelAndView modelAndView=new ModelAndView();
modelAndView.setViewName("redirect:/login");
return modelAndView;
}
//注册
@RequestMapping("register")
public ModelAndView register(User user){
ModelAndView modelAndView=new ModelAndView();
try{
userService.insert(user);
modelAndView.setViewName("redirect:/login");
}catch (Exception e){
modelAndView.setViewName("redirect:/register");
}
return modelAndView;
}
}
resource目录中的UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.young.shiro03.mapper.UserMapper">
<select id="getUserByUsername" parameterType="string" resultType="com.young.shiro03.entity.User">
select * from user
where username=#{username}
</select>
<insert id="insert" parameterType="com.young.shiro03.entity.User">
insert into user
values(#{id},#{username},#{password},#{salt},#{role})
</insert>
</mapper>
templates目录下的html文件
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>主页</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../../static/css/pico.min.css" th:href="@{/css/pico.min.css}">
<style>
#bodyLeft{
width:20%;
border-right:1px solid gray;
float:left;
height:100%;
margin-left:5%;
}
#bodyRight{
width:65%;
float:right;
margin-right:5%;
height:100%;
}
#foot{
width:100%;
height:100px;
font-size:12px;
position:absolute;
bottom:0px;
left:0px;
background:#333;
}
#foot ul{
margin-left:20px;
}
#foot li{
color:white;
}
</style>
</head>
<body>
<div id="bodyLeft">
<h1>管理</h1>
<ul>
<li><a th:href="@{/user/login}">退出登录</a></li>
<shiro:hasRole name="admin">
<li>
<a href="">用户管理</a>
</li>
<li>
<a href="">图书管理</a>
</li>
</shiro:hasRole>
<shiro:hasAnyRoles name="admin,user">
<li><a href="">图书借阅</a></li>
<li><a href="">我的借阅</a></li>
</shiro:hasAnyRoles>
</ul>
</div>
<div id="bodyRight">
<form th:action="@{/user/modifyUser}" method="post">
<label for="username">
用户名<input type="text" id="username" value="${user.username}"/>
</label>
<label for="password">
密码<input type="password" id="password" value="${user.password}"/>
</label>
<input type="submit" value="修改"/>
</form>
</div>
<div id="foot">
<ul>
<li>@CopyRight</li>
<li>版权所有:Young</li>
<li>2022-5-25</li>
</ul>
</div>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>login</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../../static/css/pico.min.css" th:href="@{/css/pico.min.css}">
</head>
<body>
<div class="container">
<form th:action="@{/user/login}" method="post">
<label for="username">
用户名<input type="text" id="username" name="username" placeholder="请输入用户名"/>
</label>
<label for="password">
密码<input type="password" id="password" name="password" placeholder="请输入密码"/>
</label>
<input type="submit" value="登录"/>
</form>
<a th:href="@{/register}" style="float:right">注册--></a>
</div>
</body>
</html>
register.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>注册</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../../static/css/pico.min.css" th:href="@{/css/pico.min.css}">
</head>
<body>
<div class="container">
<form th:action="@{/user/register}" method="post">
<label for="username">
用户名<input type="text" id="username" name="username"/>
</label>
<label for="password">
密码<input type="password" id="password" name="password"/>
</label>
<input type="submit" value="注册"/>
</form>
<a th:href="@{/login}" style="float:left;"><--返回</a>
</div>
</body>
</html>
application.yml配置文件
spring:
thymeleaf:
prefix: classpath:/templates/
suffix: .html
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/shiro?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
效果如下:
这里的用户名没传好,有个小bug,先留着,后续再修改