弄了一天的shiro,来总结一下springboot整合shiro
首先创建一个普通的springboot项目,配置上web和thymeleaf
项目的最终目录如下:
第一步:运行这个工程,看看能否正常运行
测试成功,开始正式搭建项目
1.首先导入依赖,方便起见,我把需要的依赖都放在这里了
<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.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring-boot-web-starter -->
<!-- <dependency>-->
<!-- <groupId>org.apache.shiro</groupId>-->
<!-- <artifactId>shiro-spring-boot-web-starter</artifactId>-->
<!-- <version>1.8.0</version>-->
<!-- </dependency>-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>
</dependencies>
1.建包
resource目录下的mapper包我们需要在yml文件当中绑定它的位置,否则spring容器无法扫描到它
2.新建application.yml文件,这里方便起见,我把所有的配置都放在下面了,根据自己的情况来改就好
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&useSSL=false&characterEncoding=utf-8
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
# springboot默认不注入一下的属性 需要自己绑定
#druid 数据源专有配置
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis:
# 包别名配置
type-aliases-package: com.fannnnn.pojo
# 扫描包
mapper-locations: classpath:mapper/*.xml
3.连接数据库,这里不多赘述
4.依次建实体类、mapper、*Mapper.xm、service、serviceImpl,这里我只写了一些后续要用到的方法,如果自己要去测的话可以加方法
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
private String perms;
}
@Mapper
@Repository
public interface UserMapper {
//通过username获取用户
User getUserByName(String name);
//通过username获取用户权限
String getPerms(String username);
}
<?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.fannnnn.mapper.UserMapper">
<!-- <select id="queryUserList" resultType="User">-->
<!-- select * from user;-->
<!-- </select>-->
<select id="getUserByName" resultType="User" parameterType="java.lang.String">
select * from user where name=#{name}
</select>
<select id="getPerms" parameterType="java.lang.String" resultType="java.lang.String">
select perms from `user` where name = #{name}
</select>
</mapper>
public interface UserService {
User getUserByName(String name);
String getPerms(String username);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User getUserByName(String name) {
return userMapper.getUserByName(name);
}
@Override
public String getPerms(String username) {
return userMapper.getPerms(username);
}
}
最后来测试,看看我们的方法能不能从数据库中拿出我们需要的数据
@SpringBootTest
class SpringbootShiro2ApplicationTests {
@Autowired
UserServiceImpl userService;
@Test
void contextLoads() {
System.out.println(userService.getUserByName("吴宇擘"));
System.out.println("他的权限是"+userService.getPerms("余德准"));
}
}
结果发现确实查出了我们想要的东西,好了,数据拿到了,那可以来做权限管理了
5.建一个index.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
</body>
</html>
编写路由
//可以对首页进行多个路径的设置
@RequestMapping({"/","index","index.html"})
public String index(){
return "index";
}
运行项目请求成功
6.接下来来多写几个页面以及修改原来的 index.html,为了之后的权限认证
index.html
<!DOCTYPE html>
<!--注意不要忘了thymeleaf-->
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
<a th:href="@{/user/add}">add</a> | <a th:href="@{/user/delete}">delete</a>
</body>
</html>
add.html和delete.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>add</h1>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>delete</h1>
</body>
</html>
最后再来编写Controller层
@RequestMapping("/user/add")
public String add(){
return "user/add";
}
@RequestMapping("/user/delete")
public String delete(){
return "user/delete";
}
测试结果可以正常跳转
7.在config包当中开始配置shiro,shiro最重要的三个对象,subject(表示当前对象), securityManager(管理用户)以及realm
realm类我们单独拿出来管理
UserRealm.java
public class UserRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("执行了授权");
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了认证");
return null;
}
}
shiroConfig.java
@Configuration
public class shiroConfig {
//ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
Map<String, String> filterMap = new LinkedHashMap<>();
//添加shiro的内置过滤器
/*
* anno 无需认证即可访问
* authc 必须认证了才能访问
* user 必须拥有 记住我 功能才能用
* perms 拥有对某个资源的权限才能访问
* role : 拥有某个角色权限
* */
//设置认证,表示必须认证才能访问以下两个请求
// filterMap.put("/user/add","authc");
// filterMap.put("/user/delete","authc");
//支持通配符
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
return bean;
}
//securityManager
@Bean(name = "securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
//realm
@Bean(name = "userRealm")
public UserRealm userRealm(){
return new UserRealm();
}
}
三个方法所返回的对象具有紧密的联系性,以上设置完毕后我们发现以及访问不了add和delete页面,说明请求成功了
我们来改进,当没有认证时,点击页面链接则直接跳转到登录页面,只要写好登录页,登录页的接口,最后将接口配置进shiroFilterFactoryBean中即可
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录</h1>
<form th:action="@{/login}">
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="password" name="password"></p>
<input type="submit" value="登录">
</form>
</body>
</html>
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
shiroConfig加入下面这段代码,表示无认证时,跳转到登录页面
//无认证时,跳转到登录页面
bean.setLoginUrl("/toLogin");
测试以后发现,在不登录的情况下确实会直接跳转到登录页面
接下来实现shiro的用户认证,用户认证我的理解就是是否登录
所以写好登录接口
@RequestMapping("/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";
}
}
认证方法,取出数据库中的对象并判断
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了认证");
//拿到token
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
User user = userService.getUserByName(userToken.getUsername());
if(user==null){ //没有这个人
return null;
}
//自动验证密码
return new SimpleAuthenticationInfo("",user.getPwd(),"");
}
可以发现登录实现了拦截功能以及数据库中的用户才能够正常登录
接下来做授权实现
@RequestMapping("/noauth")
@ResponseBody
public String unauthorized(){
return "未经授权,无法访问此页面";
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
// SimpleAuthorizationInfo
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// info.addStringPermission("perms:add");
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal(); //拿到user对象
info.addStringPermission(user.getPerms());
return info;
}
shiroFilterFactoryBean中添加授权
//设置授权,正常的情况下 没有授权会跳转到未授权页面 授权必须写在认证上面
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/delete","perms[user:delete]");
设置未授权
//设置未授权
bean.setUnauthorizedUrl("/noauth");
最后一步来整合thymeleaf,因为不同权限的人应该只展示他们能看的内容,所以要在thymeleaf当中判断是否将某一内容展示给用户
修改index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
<!--session当中存在loginUser时出现-->
<div th:if="${session.loginUser==null}">
<a th:href="@{/toLogin}" >登录</a>
</div>
<p th:text = "${msg}"></p>
<hr>
<!--用户有user:add权限显示add,有user:delete权限时,显示delete方法-->
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:delete">
<a th:href="@{/user/delete}">delete</a>
</div>
</body>
</html>
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了认证");
//拿到token
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
User user = userService.getUserByName(userToken.getUsername());
if(user==null){ //没有这个人
return null;
}
//设置session
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.setAttribute("loginUser",user);
//自动验证密码
return new SimpleAuthenticationInfo(user,user.getPwd(),"");
}
shiroConfig当中增加bean
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
``
最后测试,成功实现权限设置,不同权限的用户只能看不同的内容
最后再来实现注销功能
index.html中添加注销按钮
<!--sesson当中存在loginUser时显示-->
<div th:if="${session.loginUser!=null}">
<a th:href="@{/logout}">注销</a>
</div>
写好接口
@RequestMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "login";
}
测试成功!