安全框架Shiro十分钟极速入门——springBoot整合Shiro实现认证+授权

摘要

  在平时的日常开发当中,系统的认证和授权等安全相关的业务是必不可少的一部分,很多公司都是自己简单的造个轮子或者面向过程编程,导致相关的业务逻辑分散,不利于相关模块的扩展和维护,所以本篇博客将介绍一款常用的安全框架,可以帮助我们集中管理相关逻辑,让代码更加规整简洁,利于维护。目前市面上常用的安全框架主要有两种,apacher的Shiro和spring的Spring SecurityShiro相比Spring Security而言更加轻便并且容易上手,并且也基本可以满足绝大多数系统的业务需求,所以本篇博客为大家带来安全框架Shiro的极速入门教程。

Shiro的几个基本概念

  正式搭建项目之前,Shiro的几个概念需要做一下简单了解:
1.ShiroFilterFactoryBean:Shiro的过滤器,拦截url请求。
2.SecurityManager:Shiro的安全管理器,负责设置各种功能组件。
3.Realm:通过继承AuthorizingRealm,自定义认证和授权逻辑。
4.Subject:封装各种认证信息。

整合springboot

pom.xml引入相关依赖

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<!-- 继承Spring Boot的默认父工程 -->
	<!-- Spring Boot 父工程 -->
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.4.RELEASE</version>
	</parent>

	<groupId>com.itheima</groupId>
	<artifactId>springboot-shiro</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<!-- 导入依赖 -->
	<dependencies>
		<!-- 导入web支持:SpringMVC开发支持,Servlet相关的程序 -->
		<!-- web支持,SpringMVC, Servlet支持等 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- 导入thymeleaf依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<!-- shiro与spring整合依赖 -->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.4.0</version>
		</dependency>
	</dependencies>

	<!-- 修改参数 -->
	<properties>
		<!-- 修改JDK的编译版本为1.8 -->
		<java.version>1.8</java.version>
		<!-- 修改thymeleaf的版本 -->
		<thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
		<thymeleaf-layout-dialect.version>2.0.4</thymeleaf-layout-dialect.version>
	</properties>
</project>

添加登陆等页面

  所有页面放到templates文件夹下,因为使用了Thymeleaf模版,当然也可以放在static下面直接访问,但是没选择这么做,哈哈。。
页面结构如下(test1是多余的):
页面结构
  登陆页面 login.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h3>登录</h3>
<h3 th:text="${msg}" style="color: red"></h3>

<form method="post" action="login">
	用户名:<input type="text" name="name"/><br/>
	密码:<input type="password" name="password"/><br/>
	<input type="submit" value="登录"/>
</form>
</body>
</html>

  未授权提示页面 noAuth.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>未授权提示页面</title>
</head>
<body>
亲,你未经授权访问该页面
</body>
</html>

  测试页面 noAuth.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试使用</title>
</head>
<body>
<hr/>
进入用户添加功能: <a href="add">用户添加</a><br/>
进入用户更新功能: <a href="update">用户更新</a><br/>
<a href="toLogin">登录</a>
</body>
</html>

  添加页面 add.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用户添加页面</title>
</head>
<body>
用户添加
</body>
</html>

  更新页面

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用户更新页面</title>
</head>
<body>
用户更新
</body>
</html>

  页面都十分简单,相信你已经猜出来了我们要做的事情:通过Shiro控制用户登陆,登陆成功后点击添加或更新,判断用户有无添加或更新的权限,如果有,进入相应页面;没有,进入未授权页面。

添加ShiroConfig配置类

  ShiroFilterFactoryBean中注入securityManager,securityManager中注入userRealm,userRealm就是我们需要自己实现的业务逻辑。

package com.itheima.shiro;

import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;

/**
 * Shiro的配置类
 * @author lenovo
 *
 */
@Configuration
public class ShiroConfig {

	/**
	 * 创建ShiroFilterFactoryBean
	 */
	@Bean
	public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		
		//设置安全管理器
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		
		//添加Shiro内置过滤器
		/**
		 * Shiro内置过滤器,可以实现权限相关的拦截器
		 *    常用的过滤器:
		 *       anon: 无需认证(登录)可以访问
		 *       authc: 必须认证才可以访问
		 *       user: 如果使用rememberMe的功能可以直接访问
		 *       perms: 该资源必须得到资源权限才可以访问
		 *       role: 该资源必须得到角色权限才可以访问
		 */
		Map<String,String> filterMap = new LinkedHashMap<String,String>();
		
		//放行login.html页面
		filterMap.put("/login", "anon");
		
		//授权过滤器
		//注意:当前授权拦截后,shiro会自动跳转到未授权页面
		filterMap.put("/add", "perms[user:add]");
		filterMap.put("/update", "perms[user:update]");
		
		filterMap.put("/*", "authc");
		
		//修改调整的登录页面
		shiroFilterFactoryBean.setLoginUrl("/toLogin");
		//设置未授权提示页面
		shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");
		
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
		
		
		return shiroFilterFactoryBean;
	}
	
	/**
	 * 创建DefaultWebSecurityManager
	 */
	@Bean(name="securityManager")
	public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		//关联realm
		securityManager.setRealm(userRealm);
		return securityManager;
	}
	
	/**
	 * 创建Realm
	 */
	@Bean(name="userRealm")
	public UserRealm getRealm(){
		return new UserRealm();
	}
}

UserRealm类

  在这里我把用户名和密码以及授权相关的操作写死了,因为mac电脑上没有数据库,不想折腾了。。。但是连接数据库的代码已经提供(被注释掉了),思路是一样的!这里用户名为test,密码为123,并且登陆成功只添加了add权限,所以测试的时候update会跳转到无授权页面才对。

package com.itheima.shiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

import com.itheima.domain.User;
import com.itheima.service.UserService;

/**
 * 自定义Realm
 *
 * @author lenovo
 */
public class UserRealm extends AuthorizingRealm {


	@Autowired
    private UserService userSerivce;
    /**
     * 执行授权逻辑
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
        System.out.println("执行授权逻辑");

        //给资源进行授权
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //添加资源的授权字符串
        info.addStringPermission("user:add");
        Subject subject = SecurityUtils.getSubject();
        String user = (String) subject.getPrincipal();
        System.out.println("当前被授权的用户是:"+user);

        //到数据库查询当前登录用户的授权字符串
        //获取当前登录用户
//        Subject subject = SecurityUtils.getSubject();
//        User user = (User) subject.getPrincipal();
//        User dbUser = userSerivce.findById(user.getId());
//
//        info.addStringPermission(dbUser.getPerms());

        return info;
    }

    /**
     * 执行认证逻辑
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
        System.out.println("执行认证逻辑");

        //编写shiro判断逻辑,判断用户名和密码
        //1.判断用户名
        UsernamePasswordToken token = (UsernamePasswordToken) arg0;

        if (!"test".equals(token.getUsername())) {
            return null; //shiro底层会抛出UnKnowAccountException
        }

        return new SimpleAuthenticationInfo("test", "123", "");

        //数据库连接的方式判断
//        User user = userSerivce.findByName(token.getUsername());
//
//        if (user == null) {
//            //用户名不存在
//            return null;//shiro底层会抛出UnKnowAccountException
//        }
//
//        //2.判断密码
//        return new SimpleAuthenticationInfo(user, user.getPassword(), "");
    }

}

controller类

  访问页面的链接为了方便直接在controller里配置了,规范操作应该是写一个配置类去addview,相信大家都知道。另外,代码中,Subject的login方法的注释留意一下。

package com.itheima.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.itheima.service.UserService;

@Controller
public class UserController {

	/**
	 * 测试方法
	 */
	@RequestMapping("/hello")
	@ResponseBody
	public String hello(){
		System.out.println("UserController.hello()");
		return "ok";
	}
	
	@RequestMapping("/add")
	public String add(){
		return "/user/add";
	}
	
	@RequestMapping("/update")
	public String update(){
		return "/user/update";
	}
	
	@RequestMapping("/toLogin")
	public String toLogin(){
		return "/login";
	}
	
	@RequestMapping("/noAuth")
	public String noAuth(){
		return "/noAuth";
	}

	/**
	 * 测试thymeleaf
	 */
	@RequestMapping("/test")
	public String test(){
		
		//返回test.html
		return "test";
	}
	
	/**
	 * 登录逻辑处理
	 */
	@RequestMapping("/login")
	public String login(String name,String password,Model model){
		System.out.println("name="+name);
		/**
		 * 使用Shiro编写认证操作
		 */
		//1.获取Subject
		Subject subject = SecurityUtils.getSubject();
		
		//2.封装用户数据
		UsernamePasswordToken token = new UsernamePasswordToken(name,password);
		
		//3.执行登录方法
		try {
		//对自动调用UserRealm中的认证方法,认证通过后,再次访问链接会调用授权方法
			subject.login(token);
			//登录成功
			//跳转到test.html
			return "redirect:/test";
		} catch (UnknownAccountException e) {
			//e.printStackTrace();
			//登录失败:用户名不存在
			model.addAttribute("msg", "用户名不存在");
			return "login";
		}catch (IncorrectCredentialsException e) {
			//e.printStackTrace();
			//登录失败:密码错误
			model.addAttribute("msg", "密码错误");
			return "login";
		}
	}
}

测试

  启动成功,访问**http://localhost:8080/**会直接跳转到登陆页面,说明过滤器已经起了作用,随后输入错误的用户名,提示用户名错误,输入错误密码,提示密码错误(实际项目直接提示用户名或密码错误即可),后台也执行了UserRealm中的认证逻辑,如图:
用户名不存在
密码错误
后台
  输入正确的用户名和密码登陆进去:
test
  点击用户添加,授权成功:
添加
授权成功
  点击用户更新,跳往未授权页面:
未授权
  好,总体测试成功!

小结

  本篇博客通过一个非常简单的小案例讲解了shiro的基本用法,仔细看一下相信大家都能看懂,实际上shiro的功能还有很多,包括加盐认证,会话管理等。。。值得注意的是,本案例的写法是不支持前后端分离的,由于shiro每次权限不足的时候默认会重定向到权限不足的页面,而前后端分离模式下需要返回json给前台,所以肯定会出现问题,除此之外,还会出现跨域的问题,通过重写过滤器中的isAccessAllowed和onAccessDenied方法可以解决上述问题,至于具体的实现,有需要的朋友可以自行百度,师父领进门,修行靠个人嘛!学海无涯,学的越多,懂得越少,希望本篇博客可以对你有所帮助,再见!

发布了26 篇原创文章 · 获赞 99 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/m0_37719874/article/details/103839156