目录
Shiro
项目总结
新建一个SpringBoot项目
pom.xml
application.properties(配置文件)
User(实体类)
UserMapper(数据访问层接口)
UserMapper.xml(数据库映射文件)
UserService(服务层接口)
UserServiceImpl(接口实现类)
UserRealm(认证、授权逻辑代码类)
ShiroConfig(Shiro配置类)
ShiroApplication(启动类)
index.html(主页面)
login.html(登录页面)
add.html(添加页面)
delete.html(删除页面)
项目测试
Shiro
- Apache Shiro是一个开源的轻量级的Java安全框架,它提供身份验证、授权、密码管理以及会话管理等功能。相对于Spring Security,Shiro框架更加直观、易用,同时也能提供健壮的安全性
- 在实际工作时可能使用小而简单的Shiro就足够了,不存在Shiro和Security哪个更好
- Shiro支持的功能:
- Authentication:用户身份认证、登录,验证用户是否拥有相应的身份
- Authorization:授权,验证某个已认证的用户是否拥有某个权限
- Session Management:会话管理,用户登录后就是第一次会话,在没有退出之前,用户的所有信息都在会话中
- Cryptography:加密,保证数据的安全性,如将密码加密存储到数据库中
- Web Support:Web支持,使系统 可以非常容易地集成到Web环境中
- Caching:缓存,比如用户登录后,其用户信息,以及拥有的角色、权限不必每次都去查,这样可以提高效率
- Concurrency:Shiro支持多线程应用的并发验证,如在一个线程中开启另一个线程,能把权限自动地传播过去
- Testing:提供测试支持
- Run As:允许一个用户冒用另一个用户的身份(如果他们允许)进行访问
- Remember Me:一次登录后,之后登录不用输入用户名和密码
项目总结
该项目是SpringBoot整合Shiro实现了用户管理系统中的登录认证和授权
需求分析: 首先,明确项目的安全需求,包括用户认证、权限管理、会话管理等方面的具体要求。了解需要保护的资源和不同用户角色的访问权限。
项目初始化: 使用 Spring Initializr 或其他工具创建一个新的 Spring Boot 项目。配置基本的项目结构和依赖管理(Maven 或 Gradle)。
引入依赖: 在项目的
pom.xml
文件中添加 Shiro 的依赖项配置 Shiro: 创建一个 Shiro 配置类,用于配置安全管理器、Realm 和拦截器链等。
实现 Realm: 创建自定义的 Realm 类,用于处理用户认证和授权逻辑。
编写登录和权限控制代码: 创建控制器来处理用户登录和注销请求。
测试与调试: 运行项目并进行各种测试,确保用户认证和授权功能正常工作。
新建一个SpringBoot项目
新建数据库、表
CREATE DATABASE shiro;
USE shiro;
CREATE TABLE IF NOT EXISTS `t_user`(
`id` INT PRIMARY KEY COMMENT '用户ID(主键)',
`username` VARCHAR(255) UNIQUE NOT NULL COMMENT '用户名',
`password` VARCHAR(20) NOT NULL COMMENT '登录密码',
`permissions` VARCHAR(100) NOT NULL COMMENT '用户权限'
)COMMENT='用户信息表';
INSERT INTO t_user(id,username,password,permissions) VALUE (1,'zhangsan','123','user:delete');
INSERT INTO t_user(id,username,password,permissions) VALUE (2,'lisi','123','user:add');
项目结构:
pom.xml
<?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.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.study</groupId>
<artifactId>shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shiro</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<!--MyBatis逆向工程-->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
</dependency>
<!--@Data注解-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--为了在Thymeleaf中使用shiro标签,所以引入 thymeleaf-extras-shiro依赖-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.1.0</version>
</dependency>
<!--shiro安全认证框架-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.9.0</version>
</dependency>
<!--使用.html模板-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties(配置文件)
# 配置shiro的基本信息
# 表示开启Shiro配置,默认为true
shiro.enabled=true
# 表示开启Shiro Web配置,默认为true
shiro.web.enabled=true
# 表示登录地址,否则默认为"/login.jsp"
shiro.loginUrl=/login
# 表示登录成功地址,默认为"/"
shiro.successUrl=/index
# 表示未获授权默认跳转地址
shiro.unauthorizedUrl=/unauthorized
# 表示是否允许通过
shiro.sessionManager.sessionIdUrlRewritingEnabled=true
# UTL参数实现会话跟踪,如果网站支持Cookie,可以关闭此选项
# 表示是否允许通过Cookie实现会话跟踪,默认为true
shiro.sessionManager.sessionIdCookieEnabled=true
# MySQL数据库的配置信息
spring.datasource.username=root
spring.datasource.password=admin
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# mybatis的配置信息
# .xml文件放置处,此处指resources文件夹下
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.study.shiro.entity
User(实体类)
package com.study.shiro.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private String username;
private String password;
private String permissions;
}
UserMapper(数据访问层接口)
package com.study.shiro.mapper;
import com.study.shiro.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface UserMapper{
public User findUserByName(String username);
}
UserMapper.xml(数据库映射文件)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.study.shiro.mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.study.shiro.entity.User">
<id property="id" column="id" jdbcType="INTEGER"/>
<result property="username" column="username" jdbcType="VARCHAR"/>
<result property="password" column="password" jdbcType="VARCHAR"/>
<result property="permissions" column="permissions" jdbcType="VARCHAR"/>
</resultMap>
<sql id="Base_Column_List">
id,username,password,
permissions
</sql>
<select id="findUserByName" resultType="com.study.shiro.entity.User">
select * from t_user where username like #{username}
</select>
</mapper>
UserService(服务层接口)
package com.study.shiro.service;
import com.study.shiro.entity.User;
public interface UserService{
public User findUserByName(String username);
}
UserServiceImpl(接口实现类)
package com.study.shiro.service.impl;
import com.study.shiro.entity.User;
import com.study.shiro.mapper.UserMapper;
import com.study.shiro.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User findUserByName(String username) {
return userMapper.findUserByName(username);
}
}
UserRealm(认证、授权逻辑代码类)
package com.study.shiro.config;
import com.study.shiro.entity.User;
import com.study.shiro.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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;
/**
* 实现用户登录认证逻辑
*/
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
/**
* 用户授权的逻辑代码
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了===>用户授权");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//获取当前登录的这个对象
Subject subject = SecurityUtils.getSubject();
//获取User对象
User currentUser = (User) subject.getPrincipal();
//设置权限
info.addStringPermission(currentUser.getPermissions());
return info;
}
/**
* 用户认证的逻辑代码
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了===>用户认证");
UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
//连接真实的数据库,调取用户信息
User user = userService.findUserByName(token.getUsername());
//该用户不存在
if(user==null){
return null;
}
Subject subject = SecurityUtils.getSubject();
//将登录用户放入Session中
subject.getSession().setAttribute("loginUser",user);
//密码认证
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
}
ShiroConfig(Shiro配置类)
package com.study.shiro.config;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Qualifier;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
/**
* ShiroDialect类是为了支持在Thymeleaf中使用的Shiro标签
*/
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(securityManager);
// Shiro内置过滤器,可以实现权限相关的拦截器
/*
常用的过滤器:
anon: 无需认证(登录)可以访问
authc: 必须认证才可以访问
user: 如果使用rememberMe的功能可以直接访问
perms: 该资源必须得到资源权限才可以访问
role: 该资源必须得到角色权限才可以访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/login", "anon");
filterMap.put("/index", "anon");
filterMap.put("/doLogin", "anon"); // 无需认证即可访问
filterMap.put("/logout", "logout"); // Shiro自带的退出登录
filterMap.put("/**", "authc"); // 拦截其他所有请求,需要认证
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/delete","perms[user:delete]");
// 设置默认登录的 URL,身份认证失败会访问该 URL
bean.setLoginUrl("/login");
// 设置登录成功后要跳转的链接
bean.setSuccessUrl("/index");
//未授权时跳转的页面
bean.setUnauthorizedUrl("/noauth");
bean.setFilterChainDefinitionMap(filterMap);
return bean;
}
@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 userRealm() {
return new UserRealm();
}
}
UserController(控制器)
package com.study.shiro.controller;
import org.apache.shiro.SecurityUtils;
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.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class UserController {
//主页面
@RequestMapping({"/index","/"})
public String index(Model model){
model.addAttribute("msg","hello shiro!");
return "index";
}
//登录页面
@RequestMapping("/login")
public String login(){
return "login";
}
//处理登录请求,是否成功
@RequestMapping("/doLogin")
public String doLogin(String username,String password,Model model){
//封装用户数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//获取当前用户
Subject currentUser = SecurityUtils.getSubject();
//执行登录的方法,只要没有异常就代表登录成功
try {
currentUser.login(token);
return "index";
} catch (UnknownAccountException uae) {
model.addAttribute("msg","用户名不存在!");
return "login";
}catch (IncorrectCredentialsException ice){
model.addAttribute("msg","密码错误!");
return "login";
}
}
//注销
@RequestMapping("/logout")
public String logout(){
Subject currentUser = SecurityUtils.getSubject();
currentUser.logout();
return "index";
}
@RequestMapping("/noauth")
@ResponseBody
public String noAuth(){
return "未经授权不能访问此页面";
}
//获取指定授权后,可访问该页面
@RequestMapping("/user/add")
public String add(){
return "/user/add";
}
//获取指定授权后,可访问该页面
@RequestMapping("/user/delete")
public String delete(){
return "/user/delete";
}
}
ShiroApplication(启动类)
package com.study.shiro;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ShiroApplication {
public static void main(String[] args) {
SpringApplication.run(ShiroApplication.class, args);
}
}
index.html(主页面)
<!DOCTYPE html>
<html lang="en" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>主页面</title>
<!--引入Bootstrap国内CDN库-->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-4 col-md-offset-3">
<h1>主页面</h1>
</div>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-3">
<!--会话中没有用户,即用户未登录,显示"登录"超链接-->
<p th:if="${session.loginUser == null}">
<a th:href="@{/login}">登录</a>
</p>
</div>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-3">
<!--会话中保存了用户,即用户已登录,显示"注销"超链接-->
<p th:if="${session.loginUser != null}">
<a th:href="@{/logout}">注销</a>
</p>
</div>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-3">
<div shiro:haspermission="user:add">
<a th:href="@{/user/add}">添加</a>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-3">
<div shiro:haspermission="user:delete">
<a th:href="@{/user/delete}">删除</a>
</div>
</div>
</div>
</div>
</body>
</html>
login.html(登录页面)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-4 col-md-offset-3">
<h1>登录页面</h1>
</div>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-3">
<p style="color:red;" th:text="${msg}"></p>
</div>
</div>
<form class="form-horizontal" th:action="@{/doLogin}" method="post">
<div class="form-group">
<label class="col-sm-2 control-label">用户名</label>
<div class="col-sm-4">
<input type="text" class="form-control" name="username">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">密码</label>
<div class="col-sm-4">
<input type="password" class="form-control" name="password">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">登录</button>
</div>
</div>
</form>
</div>
</body>
</html>
add.html(添加页面)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加页面</title>
<!--引入Bootstrap国内CDN库-->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-4 col-md-offset-5"><h1>添加</h1></div>
</div>
</div>
</body>
</html>
delete.html(删除页面)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>删除页面</title>
<!--引入Bootstrap国内CDN库-->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-4 col-md-offset-5"><h1>删除</h1></div>
</div>
</div>
</body>
</html>
项目测试
1、访问主页面:localhost:8080/index,点击“登录”
2、输入用户信息,登录
3、用户zhangsan拥有“删除”的权限
4、点击“注销”,返回用户登录页面
5、用户lisi,拥有“添加”的权限