Apache shiro是一个开源的轻量级Java安全框架,它提供身份验证,授权,密码管理以及会话管理等功能。相对于Spring Security,Shiro更加直观,易用,同时也能提供健壮的安全性。
下面开始springboot整合shiro,github:https://github.com/fengqing11/springboot-shiro
创建项目,添加依赖:
注意这里不需要添加spring-boot-stater-web依赖,shiro-spring-boot-web-stater中依赖了spring-boot-stater-web。同时,本案例使用thymeleaf模板,需要添加thymeleaf依赖。另外thymeleaf中使用了shiro标签,需要添加thymeleaf-extras-shiro依赖。
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>xyz.fengqing11</groupId>
<artifactId>springboot-shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-shiro</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
shiro基本配置:
shiro.enabled=true
shiro.web.enabled=true
shiro.loginUrl=/login
shiro.successUrl=/index
shiro.unauthorizedUrl=/unauthorized
shiro.sessionManager.sessionIdUrlRewritingEnabled=true
shiro.sessionManager.sessionIdCookieEnabled=true
基本信息配置完成后还需要在java代码中配置shiro:
提供两个基本bean,Realm和ShiroFilterChainDefinition。至于ShiroDialect是为了在thymeleaf中能支持shiro标签。如果不使用shiro标签,可以不使用ShiroDialect。
Realm可以自定义Realm,也可以是shiro提供Realm。简单起见,本案例没有配置数据库连接,这里直接配置两个用户,分别对应角色admin和user。
ShiroFilterChainDefinition中配置基本过滤规则,/login和/doLogin可以匿名访问,/logout是一个注销登入请求,其余访问都需要认证后才能访问。
package xyz.fengqing11.springbootshiro.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.text.TextConfigurationRealm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import sun.awt.SunHints;
@Configuration
public class ShiroConfig {
@Bean
public Realm realm() {
TextConfigurationRealm realm = new TextConfigurationRealm();
realm.setUserDefinitions("sang=123,user\n admin=123,admin");
realm.setRoleDefinitions("admin=read,write \n user=read");
return realm;
}
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition(){
DefaultShiroFilterChainDefinition chainDefinition =
new DefaultShiroFilterChainDefinition();
chainDefinition.addPathDefinition("/login","anon");
chainDefinition.addPathDefinition("/doLogin","anon");
chainDefinition.addPathDefinition("/logout","logout");
chainDefinition.addPathDefinition("/**","authc");
return chainDefinition;
}
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
登入接口,以及页面访问:
doLogin方法中,先构造一个UsernamePasswordToken实例,然后创建一个Subject对象,并调用该对象的login方法实现登入操作,登入失败则携带错误信息,并返回登入页面,登入成功则跳转到index页面。
对于/admin接口,需要admin才能访问。
对于/user接口,admin和user都能访问。
package xyz.fengqing11.springbootshiro.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class UserCnntroller {
@PostMapping("doLogin")
public String doLogin(String username, String password, Model model){
UsernamePasswordToken token =
new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
}catch (AuthenticationException e){
model.addAttribute("error","用户名或密码输入错误!");
return "login";
}
return "redirect:/index";
}
@RequiresRoles("admin")
@GetMapping("admin")
public String admin(){
return "admin";
}
@RequiresRoles(value = {"admin","user"},logical = Logical.OR)
@GetMapping("/user")
public String user(){
return "user";
}
}
配置WebMvc:
配置一些不需要角色就嫩访问的页面。
package xyz.fengqing11.springbootshiro.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.addViewController("index").setViewName("index");
registry.addViewController("/unauthorized").setViewName("unauthorized");
}
}
全局异常处理:
处理授权异常。
package xyz.fengqing11.springbootshiro.config;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(AuthorizationException.class)
public ModelAndView error(AuthorizationException e) {
ModelAndView mv = new ModelAndView("unauthorized");
mv.addObject("error", e.getMessage());
return mv;
}
}
常见视图页面:
位于templates下。
index.html
<!DOCTYPE html>
<html lang="en" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<h3>hello,<shiro:principal/></h3>
<h3><a href="/logout">注销登入</a></h3>
<h3><a shiro:hasRole="admin" href="/admin">管理员页面</a></h3>
<h3><a shiro:hasAnyRoles="admin,user" href="/user">普通用户页面</a></h3>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<form action="/doLogin" method="post">
<input type="text" name="username"><br>
<input type="text" name="password"><br>
<div th:text="${error}"></div>
<input type="submit" value="登入">
</form>
</body>
</html>
user.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>user</title>
</head>
<body>
<h1>普通用户页面</h1>
</body>
</html>
admin.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>admin</title>
</head>
<body>
<h1>管理员页面</h1>
</body>
</html>
unauthorized.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>unauthorized</title>
</head>
<body>
<h1>未授权,非法访问</h1>
<div th:text="${error}"></div>
</body>
</html>
</html>
访问:http://127.0.0.1:8080/会自动跳转到如下页面:
我们先试着输入sang和123登入,登入成功是这样的:
可以访问普通用户页面。
等入失败是这样的:
普通用户访问管理员页面:
然后输入admin和123等入管理员页面:
-end-