目录
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 mapperPUBLIC "-//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 {@AutowiredUserMapper userMapper;@Overridepublic 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 {@AutowiredUserService userService;/*** 用户授权的逻辑代码*/@Overrideprotected 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;}/*** 用户认证的逻辑代码*/@Overrideprotected 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标签*/@Beanpublic ShiroDialect getShiroDialect() {return new ShiroDialect();}@Beanpublic 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,身份认证失败会访问该 URLbean.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();// 关联RealmsecurityManager.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")@ResponseBodypublic 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,拥有“添加”的权限