SpringBoot与Shiro整合
- SpringBoot与Shiro框架简介
- Spring Boot框架
- 什么是 Spring Boot
- Spring Boot概述
- 使用 Spring Boot 有什么好处
- Shiro框架
- Apache Shiro 体系结构
- Spring Boot快速启动
- 创建Maven工程
- 导入web支持
- 编写测试Controller类
- 创建SpringBoot启动类
- 导入thymeleaf页面模块
- Spring Boot与Shiro整合
- Mysql数据库
- 配置、依赖以及包、类、接口
- 整合实现用户认证
- 使用Shiro内置过滤器实现页面拦截
- 自定义Realm类
- 编写Shiro配置类
- 用户登录
- UserController中login的逻辑处理
- 编写Realm的判断逻辑
- 整合实现用户授权
- thymeleaf和shiro标签整合
- 这个充满BUG的小项目下载地址:
SpringBoot与Shiro框架简介
Spring Boot框架
什么是 Spring Boot
Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。Spring Boot 其实不是什么新的框架,它默认配置了很多框架的使用方式,就像 Maven 整合了所有的 Jar 包,Spring Boot 整合了所有的框架。
Spring Boot概述
Spring Boot 是所有基于 Spring 开发的项目的起点。Spring Boot 的设计是为了让你尽可能快的跑起来 Spring 应用程序并且尽可能减少你的配置文件。
使用 Spring Boot 有什么好处
回顾我们之前的 SSM 项目,搭建过程还是比较繁琐的,需要:
1)配置 web.xml,加载 spring 和 spring mvc
2)配置数据库连接、配置日志文件
3)配置加载配置文件的读取,开启注解
4)配置mapper文件
…
现在非常流行微服务,如果我这个项目仅仅只是需要发送一个邮件,如果我的项目仅仅是生产一个积分;我都需要这样折腾一遍!
但是如果使用 Spring Boot 呢?
很简单,我仅仅只需要非常少的几个配置就可以迅速方便的搭建起来一套 Web 项目或者是构建一个微服务!
划重点:简单、快速、方便地搭建项目;对主流开发框架的无配置集成;极大提高了开发、部署效率。
Shiro框架
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
Apache Shiro 体系结构
1、Authentication 认证 ---- 用户登录
2、Authorization 授权 — 用户具有哪些权限
3、Cryptography 安全数据加密
4、Session Management 会话管理
5、Web Integration web系统集成
6、Interations 集成其它应用,spring、缓存框架
Spring Boot快速启动
创建Maven工程
配置 Maven-Installations
配置 Maven-User Settings
新建Maven Project
导入spring boot父工程
<!-- 继承Spring Boot的默认父工程 -->
<!-- Spring Boot 父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
</parent>
导入web支持
<!-- 导入依赖 -->
<dependencies>
<!-- 导入web支持:SpringMVC开发支持,Servlet相关的程序 -->
<!-- web支持,SpringMVC, Servlet支持等 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
编写测试Controller类
@Controller
public class IndexController{
/**
* 测试方法
*/
@RequestMapping("/hello")
@ResponseBody
public String hello() {
System.out.println("UserController.hello()");
return "ok";
}
}
创建SpringBoot启动类
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
启动
可以看到,测试类的hello被映射出来了,Tomcat默认端口是8080
启动成功后,浏览器访问地址:http://localhost:8080/hello
导入thymeleaf页面模块
<!-- 导入thymeleaf依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
在IndexController里测试
/**
* 测试thymeleaf
*/
@RequestMapping("/testThymeleaf")
public String testThymeleaf(Model model) {
//在Model里存个测试数据
model.addAttribute("test","测试");
//返回test.html
return "test";
}
建立test.html页面
在src/main/resource目录下创建templates目录,然后创建test.html页面
<!-- 这里注意,对meta标签加了个结束,因为在thymeleaf3.0以前对页面标签语法要求比较严格,开始标签必须有对应的结束标签。 -->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>测试Thymeleaf的使用</title>
</head>
<body>
<h3 th:text="${test}"></h3>
</body>
</html>
如果希望页面语法不严谨,但是也能够运行成功,可以把thymeleaf升级为3.0或以上版本。
升级thymeleaf3.0.2版本
<!-- 修改参数 -->
<properties>
<!-- 修改thymeleaf的版本 -->
<thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.0.4</thymeleaf-layout-dialect.version>
</properties>
Spring Boot与Shiro整合
Mysql数据库
新建数据库:test
新建表:user
字段:
- Integer:id(主键,不为空,自增)
- varchar:user_name
- varchar:paasword
- varchar:role_name
建好后添加几条测试数据。
配置、依赖以及包、类、接口
在pom配置Mysql,JPA依赖和druid连接池
<!-- shiro与spring整合依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!--data jpa 起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<!-- druid 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
配置application.yml
在src/main/resources下新建文件application.yml
#开发环境
#数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&serverTimezone=UTC&useUnicode=true&useSSL=false
username: root
password: root
thymeleaf:
cache: false #关闭thymeleaf缓存
jpa:
hibernate:
ddl-auto: update
show-sql: true
新建包:entity,然后建一个User实体类
/**
* 用户实体
*/
@Entity
@Table(name="user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
private String password;
private String roleName;
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
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;
}
}
新建repository包,然后建一个UserRepository接口:
/**
* 用户 Repository接口
*/
public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {
/**
* 根据用户名查找用户实体
*/
@Query(value = "select * from user where user_name=?1",nativeQuery = true)
public User findByUserName(String userName);
}
新建service包,然后建一个UserService接口:
/**
* 用户service接口
*/
public interface UserService {
/**根据用户名查找用户实体*/
public User findByUserName(String userName);
}
新建service.impl包,然后建一个UserServiceImpl实现类:
/**
* 用户service实现类
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public User findByUserName(String userName) {
return userRepository.findByUserName(userName);
}
}
整合实现用户认证
使用Shiro内置过滤器实现页面拦截
Shiro内置过滤器,可以实现权限相关的拦截器。
- 常用的过滤器:
- anon: 无需认证(登录)可以访问
- authc: 必须认证才可以访问
- user: 如果使用rememberMe的功能可以直接访问
- perms: 该资源必须得到资源权限才可以访问
- role: 该资源必须得到角色权限才可以访问
准备两个测试页面
在controller包新建一个UserController类
@Controller
@RequestMapping(value = "/user")
public class UserController {
/**
* add
*/
@RequestMapping("/toAdd")
public String add() {
return "/user/add";
}
/**
* update
*/
@RequestMapping("/toUpdate")
public String update() {
return "/user/update";
}
}
在test.html加入跳转链接
用户的添加:<a href="/user/toAdd">添加</a>
用户的更新:<a href="/user/toUpdate">更新</a>
自定义Realm类
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;
}
}
编写Shiro配置类
/**
* Shiro配置类
*
* @author zhaoy
*
*/
@Configuration
public class ShiroConfig {
/**
* 创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加Shiro内置过滤器
/**
* Shiro内置过滤器,可以实现权限相关的拦截器
* 常用的过滤器:
* anon: 无需认证(登录)可以访问
* authc: 必须认证才可以访问
* user: 如果使用rememberMe的功能可以直接访问
* perms: 该资源必须得到资源权限才可以访问
* role: 该资源必须得到角色权限才可以访问
*/
Map<String,String> filterMap = new LinkedHashMap<String,String>();
filterMap.put("/testThymeleaf", "anon");
// filterMap.put("/user/toAdd", "authc");
// filterMap.put("/user/toUpdate", "authc");
//最后加默认全部拦截,不然会和后面添加的指定拦截
filterMap.put("/**","authc");//这个 /** 可以代替上面两个
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager(){
//安全管理器
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm
securityManager.setRealm(userRealm());
return securityManager;
}
/**
* 身份认证 realm
* @return
*/
@Bean
public UserRealm userRealm(){
UserRealm userRealm = new UserRealm();
return userRealm;
}
}
访问test页面,并进行跳转测试
拦截功能成功
filterMap.put("/testThymeleaf", “anon”); 未被拦截;
filterMap.put("/user/toAdd", “authc”); 被拦截;
filterMap.put("/user/toUpdate", “authc”); 被拦截;这里注意:页面被拦截后,默认跳转到login.jsp
改变默认跳转路径
在UserController添加一个login页面的跳转:
/**
* login
*/
@RequestMapping("/toLogin")
public String toLogin() {
return "login";
}
在templates文件下新建一个login.html页面:
<title>登录页面</title>
</head>
<body>
<h3>登录</h3>
<!-- 接收msg返回的消息 -->
<h3 th:text="${msg}" style="color: red"></h3>
<form method="post" action="login">
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" value="登录"/>
</form>
</body>
到ShiroConfig里放开login页面,并修改默认跳转的路径:
测试,默认页面改变成功:
用户登录
UserController中login的逻辑处理
/**
* 登录逻辑处理
* @param username 账号
* @param password 密码
* @param model 返回msg消息
* @return
*/
@RequestMapping("/login")
public String login(String username,String password,Model model){
/**
* 使用Shiro编写认证操作
*/
//1.获取Subject
Subject subject = SecurityUtils.getSubject();
//2.把用户名和密码封装为 UsernamePasswordToken 对象
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//3.执行登录方法
try {
subject.login(token);//执行到subject.login()时,shiro会去UserRealm执行认证逻辑。
//登录成功
//跳转到test.html
return "redirect:/testThymeleaf";
}
// 若没有指定的账户, 则 shiro 将会抛出 UnknownAccountException 异常.
catch (UnknownAccountException uae) {
//登录失败:用户名不存在
model.addAttribute("msg", "用户名不存在");
return "login";
}
// 若账户存在, 但密码不匹配, 则 shiro 会抛出 IncorrectCredentialsException 异常。
catch (IncorrectCredentialsException ice) {
//登录失败:密码错误
model.addAttribute("msg", "密码错误");
return "login";
}
// 所有认证时异常的父类.
catch (AuthenticationException ae) {
model.addAttribute("msg", "其他错误!");
return "login";
}
}
编写Realm的判断逻辑
@Autowired
private UserService userService;
/**
* 执行认证逻辑
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行认证逻辑");
//System.out.println(token.getPrincipal());
//getPrincipal()方法—获取当前记录的用户名
String username = (String) token.getPrincipal();
//根据用户名查询用户信息
User user = userService.findByUserName(username);
System.out.println("执行认证逻辑:" + user);
// 编写shiro判断逻辑 ,判断用户名和密码
// 1.判断用户
if (user == null) {
// 用户不存在
return null; // shiro底层会自动抛出UnKnowAccountException
} else {
// 2.判断密码(身份信息)
AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), "");//第一个这里放的是user对象,第二个参数必须放放数据库的密码 shiro会自动判断,(可以加一个参数,盐,用来密码加密的,暂时不讲)第三个参数为当前realm的名字
return authenticationInfo;
}
}
运行测试结果
1.用户名不存在
2.密码错误
3.登陆成功,跳转测试页面
4.登录成功后就可以进入添加、更新页面了
整合实现用户授权
在添加页面访问前加授权验证
在ShiroConfig开启shiro的生命周期以及注解
/**
* Shiro配置类
*
* @author zhaoy
*
*/
@Configuration
public class ShiroConfig {
/**
* 创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加Shiro内置过滤器
/**
* Shiro内置过滤器,可以实现权限相关的拦截器
* 常用的过滤器:
* anon: 无需认证(登录)可以访问
* authc: 必须认证才可以访问
* user: 如果使用rememberMe的功能可以直接访问
* perms: 该资源必须得到资源权限才可以访问
* role: 该资源必须得到角色权限才可以访问
*/
Map<String,String> filterMap = new LinkedHashMap<String,String>();
filterMap.put("/testThymeleaf", "anon");
filterMap.put("/user/login", "anon");
// filterMap.put("/user/toAdd", "authc");
// filterMap.put("/user/toUpdate", "authc");
//最后加默认全部拦截,不然会和后面添加的指定拦截
filterMap.put("/**","authc");//这个 /* 可以代替上面两个
//修改跳转的默认登录页面
shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager(){
//安全管理器
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm
securityManager.setRealm(userRealm());
return securityManager;
}
/**
* 身份认证 realm
* @return
*/
@Bean
public UserRealm userRealm(){
UserRealm userRealm = new UserRealm();
return userRealm;
}
/**
* shiro 生命周期
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
/**
* 开启shiro的注解(如: @RequiresRoles, @RequiresPermissions),需借助SpringAOP扫描使用shiro注解的类,并在必要的时候进行安全逻辑验证
* 配置以下两个Bean即可实现shiro这个功能
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
//默认自动代理创建程序
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
//授权属性资源管理器
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
测试发现,选非会员zhang3用户,访问添加页面失败出现未授权错误(管理员依然可以访问)
报错页面肯定不行,给他设置个未授权的被拦截页面:
1.在IndexController做一个授权不通过后跳转操作
/**
* @ControllerAdvice注解的作用:是一个Controller增强器,可对controller中被@RequestMapping注解的方法加一些逻辑处理,最常用的就是异常处理;【三种使用场景】全局异常处理。全局数据绑定,全局数据预处理
* @Order 注解@Order或者接口Ordered的作用是定义SpringIOC容器中Bean的执行顺序的优先级,而不是定义Bean的加载顺序,Bean的加载顺序不受@Order或Ordered接口的影响;
* @ExceptionHandler 统一异常处理
*
*/
@ControllerAdvice
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler {
@ExceptionHandler(value = AuthorizationException.class)
public String handleAuthorizationException() {
return "noAuth";
}
}
2. templates文件下新建noAuth.html页面
<meta charset="UTF-8">
<title>未授权提示</title>
</head>
<body>
页面被拒绝访问,因为您未被授权!
</body>
</html>
- 成功跳转至未授权页面
thymeleaf和shiro标签整合
1.pom.xml导入thymel对shiro的扩展
<!-- thymel对shiro的扩展 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
2.ShiroConfig类里配置ShiroDialect
/**
* 配置ShiroDialect,用于thymeleaf和shiro标签配合使用
*/
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
3.在test.html上使用shiro标签
4.经测试,只有登录管理员身份的账户,才能看到此链接非管理员的页面显示:
管理员的页面显示: