在平时的日常开发当中,系统的认证和授权等安全相关的业务是必不可少的一部分,很多公司都是自己简单的造个轮子或者面向过程编程,导致相关的业务逻辑分散,不利于相关模块的扩展和维护,所以本篇博客将介绍一款常用的安全框架,可以帮助我们集中管理相关逻辑,让代码更加规整简洁,利于维护。目前市面上常用的安全框架主要有两种,apacher的Shiro和spring的Spring Security,Shiro相比Spring Security而言更加轻便并且容易上手,并且也基本可以满足绝大多数系统的业务需求,所以本篇博客为大家带来安全框架Shiro的极速入门教程。
正式搭建项目之前,Shiro的几个概念需要做一下简单了解:
1.ShiroFilterFactoryBean:Shiro的过滤器,拦截url请求。
2.SecurityManager:Shiro的安全管理器,负责设置各种功能组件。
3.Realm:通过继承AuthorizingRealm,自定义认证和授权逻辑。
4.Subject:封装各种认证信息。
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>1.5.4.RELEASEversion>
parent>
<groupId>com.itheimagroupId>
<artifactId>springboot-shiroartifactId>
<version>0.0.1-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.4.0version>
dependency>
dependencies>
<properties>
<java.version>1.8java.version>
<thymeleaf.version>3.0.2.RELEASEthymeleaf.version>
<thymeleaf-layout-dialect.version>2.0.4thymeleaf-layout-dialect.version>
properties>
project>
所有页面放到templates文件夹下,因为使用了Thymeleaf模版,当然也可以放在static下面直接访问,但是没选择这么做,哈哈。。
页面结构如下(test1是多余的):
登陆页面 login.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
<html>
<head>
<meta charset="UTF-8">
<title>未授权提示页面title>
head>
<body>
亲,你未经授权访问该页面
body>
html>
测试页面 noAuth.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
<html>
<head>
<meta charset="UTF-8">
<title>用户添加页面title>
head>
<body>
用户添加
body>
html>
更新页面
<html>
<head>
<meta charset="UTF-8">
<title>用户更新页面title>
head>
<body>
用户更新
body>
html>
页面都十分简单,相信你已经猜出来了我们要做的事情:通过Shiro控制用户登陆,登陆成功后点击添加或更新,判断用户有无添加或更新的权限,如果有,进入相应页面;没有,进入未授权页面。
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();
}
}
在这里我把用户名和密码以及授权相关的操作写死了,因为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里配置了,规范操作应该是写一个配置类去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中的认证逻辑,如图:
输入正确的用户名和密码登陆进去:
点击用户添加,授权成功:
点击用户更新,跳往未授权页面:
好,总体测试成功!
本篇博客通过一个非常简单的小案例讲解了shiro的基本用法,仔细看一下相信大家都能看懂,实际上shiro的功能还有很多,包括加盐认证,会话管理等。。。值得注意的是,本案例的写法是不支持前后端分离的,由于shiro每次权限不足的时候默认会重定向到权限不足的页面,而前后端分离模式下需要返回json给前台,所以肯定会出现问题,除此之外,还会出现跨域的问题,通过重写过滤器中的isAccessAllowed和onAccessDenied方法可以解决上述问题,至于具体的实现,有需要的朋友可以自行百度,师父领进门,修行靠个人嘛!学海无涯,学的越多,懂得越少,希望本篇博客可以对你有所帮助,再见!