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

spring cloud 整合impala springcloud整合shiro_spring boot


配置 Maven-User Settings

spring cloud 整合impala springcloud整合shiro_shiro_02


spring cloud 整合impala springcloud整合shiro_spring_03


新建Maven Project

spring cloud 整合impala springcloud整合shiro_mysql_04


导入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

spring cloud 整合impala springcloud整合shiro_User_05


启动成功后,浏览器访问地址:http://localhost:8080/hello

spring cloud 整合impala springcloud整合shiro_mysql_06

导入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>

spring cloud 整合impala springcloud整合shiro_User_07

如果希望页面语法不严谨,但是也能够运行成功,可以把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: 该资源必须得到角色权限才可以访问

准备两个测试页面

spring cloud 整合impala springcloud整合shiro_User_08


在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页面,并进行跳转测试

spring cloud 整合impala springcloud整合shiro_spring boot_09


拦截功能成功

filterMap.put("/testThymeleaf", “anon”); 未被拦截;

filterMap.put("/user/toAdd", “authc”); 被拦截;

filterMap.put("/user/toUpdate", “authc”); 被拦截;这里注意:页面被拦截后,默认跳转到login.jsp

spring cloud 整合impala springcloud整合shiro_shiro_10

改变默认跳转路径

在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页面,并修改默认跳转的路径:

spring cloud 整合impala springcloud整合shiro_mysql_11

测试,默认页面改变成功:

spring cloud 整合impala springcloud整合shiro_User_12

用户登录

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.用户名不存在

spring cloud 整合impala springcloud整合shiro_spring boot_13


2.密码错误

spring cloud 整合impala springcloud整合shiro_mysql_14


3.登陆成功,跳转测试页面

spring cloud 整合impala springcloud整合shiro_shiro_15


4.登录成功后就可以进入添加、更新页面了

spring cloud 整合impala springcloud整合shiro_spring_16

整合实现用户授权

在添加页面访问前加授权验证

spring cloud 整合impala springcloud整合shiro_shiro_17


在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用户,访问添加页面失败出现未授权错误(管理员依然可以访问)

spring cloud 整合impala springcloud整合shiro_User_18

报错页面肯定不行,给他设置个未授权的被拦截页面:

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>
  1. 成功跳转至未授权页面

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标签

spring cloud 整合impala springcloud整合shiro_spring boot_19


4.经测试,只有登录管理员身份的账户,才能看到此链接非管理员的页面显示:

spring cloud 整合impala springcloud整合shiro_mysql_20

管理员的页面显示:

spring cloud 整合impala springcloud整合shiro_spring boot_21