Sringboot2整合shiro实现登录认证、权限控制和记住我功能
7 分钟
前言
很久没写过文章了,最近课程设计硬性要求要使用Spring Secure或shiro其中一种安全框架,因为shiro的轻便更被广泛使用,所以学了一下shiro框架的基本使用,也来了点兴致,所以记录一下。
提示:以下是本篇文章正文内容,下面案例可供参考
一、关于Shiro
- Apache Shiro 是一个功能强大且易于使用的Java安全框架,为开发人员提供了一种直观而全面的身份验证,授权,加密和会话管理解决方案,提供了身份验证、授权、密码学、会话管理四大基本安全功能、还提供一些缓存、记住我、并发等多种额外功能解决一些问题。
- Shiro的一些核心概念有:Realms、SecutiryManager、Subjuect。
- 关于Subject可直接理解为对象,用于和应用进行交互,通过Subject传入到安全管理器。
- 关于SecurityManger就是安全管理器,所有的安全性操作都与它进行交互,是Shiro管理的核心。
- 关于Realms可以理解为存储域,存储Shiro的一些安全数据等,安全管理器需要从Realms获取数据,验证是否合法。
大概就这些,学的比较粗浅,底层源码也没咋了解,直接就学了如何使用。
二、功能实现
1.创建User表的数据库
- 实现Shiro与数据库交互,能够通过手机号+密码或邮箱+密码进行登录验证。
这里的数据库字段设置如上、Id号(主键)、name(姓名)、phone(手机号)、username(用户名)、password(密码)、email(邮箱)、state(状态)。
2.引入依赖
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.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>shiroDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shiroDemo</name>
<description>shiroDemo</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.配置application.yaml文件
当然也可以配置application.properties,个人习惯使用yaml,这里配置mybatis和连接mysql数据库,如下:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/teamwork?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
mybatis:
#config-location: classpath:mybatis/mybatis-config.xml #全局配置文件位置
mapper-locations: classpath:mybatis/mapper/*.xml #sql映射文件位置
configuration:
map-underscore-to-camel-case: true
4.完善Mybatis
- 创建User类如下:
package com.example.shirodemo.Bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private Integer id;
private String name;
private String phone;
private String username;
private String password;
private String email;
private Integer state;
}
- 创建Mapper接口和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.example.shirodemo.mapper.UserMapper">
</mapper>
package com.example.shirodemo.mapper;
import com.example.shirodemo.Bean.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper {
@Select("select *from user where password=#{password} limit 1")
User ShiroAuthority(String password);
@Select("select password from user where phone=#{phone} ")
User Login(String phone);
@Select("select password from user where email=#{email}")
User LoginByEmail(String email);
}
这里用两个Select语句,当前端传入数据第一种方式查询不到user用户时,就进行第二种,以此来实现两种登录方式,当然也可以一个方法直接写入到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.example.homework1.mapper.UserMapper">
<select id="login" resultType="com.example.homework1.bean.User">
select *from User
<where>
<if test="phone!=null and phone!=''">
and phone=#{phone}
</if>
<if test="email!=null and email!=''">
and email=#{email}
</if>
and password= #{password}
</where>
</select>
5.重写Realm和Shiro配置类
- 因为数据库中存储的密码是用的base64+md5加密存储,所以这里要先写一个MD5的加密类,如下:
package com.example.shirodemo.config;
import org.springframework.util.DigestUtils;
import java.util.Base64;
import static com.mysql.cj.util.StringUtils.getBytes;
public class MD5 {
/**
* @param text明文
* @param key密钥
* @return 密文
* 对数据库内的密码存储进行md5的加密
*/
public static String md5(String password,String key) throws Exception {
String base64encodedString = Base64.getEncoder().encodeToString((password + key).getBytes("utf-8"));
// 加密后的字符串
return DigestUtils.md5DigestAsHex(getBytes(base64encodedString));
}
}
- 重写Realm方法
package com.example.shirodemo.config;
import com.example.shirodemo.Bean.User;
import com.example.shirodemo.mapper.UserMapper;
import com.example.shirodemo.service.UserService;
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.springframework.beans.factory.annotation.Autowired;
import java.util.Collections;
public class MyRealm extends AuthorizingRealm {
@Autowired
UserService userService;
@Autowired
UserMapper userMapper;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
User user = (User) principals.getPrimaryPrincipal();
if (user != null) {
User users = (User) userMapper.ShiroAuthority(user.getPassword());
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();//权限信息
if (users.getUsername().equals("admin")) {
System.out.println(users.getName());
simpleAuthorizationInfo.addRole("admin");
} else {
simpleAuthorizationInfo.addRole("user");
}
return simpleAuthorizationInfo;
}
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) {
String username= (String) authcToken.getPrincipal();
// 获取用户密码
User user;
user = userService.Login(username);
if (user == null) {
// 没找到帐号
user =userService.LoginByEmail(username);
if(user==null)
throw new UnknownAccountException();
}
// 交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配
return new SimpleAuthenticationInfo(user, user.getPassword(),getName());
}
}
这里主要重写两个方法,第一个方法用于进行权限验证,这里用不到,下面一个方法用于进行登录验证,主要从Token中取得用户名,然后是否存在此用户,不存在则报错,存在则交给CredentialsMatcher进行密码进行密码匹配,principals用于控制角色,通过Autoken中的对象查询数据库的用户名,判断是否是admin,添加角色用于判断。
- ShiroConfiguration类
package com.example.shirodemo.config;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfiguration {
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* Shiro生命周期处理器
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
/**
* 自动创建代理
* @return
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
解决MyRealm中UserMapper一直是null的问题
**/
@Bean
MyRealm myRealm() {
return new MyRealm();
}
/**
*安全管理器
* **/
@Bean
SecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(myRealm());
manager.setRememberMeManager(rememberMeManager());
manager.setSessionManager(sessionManager());
return manager;
}
@Bean
ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//指定 SecurityManager
bean.setSecurityManager(securityManager());
//登录页面
bean.setLoginUrl("/login");
//登录成功页面
bean.setSuccessUrl("/");
//访问未获授权路径时跳转的页面
bean.setUnauthorizedUrl("/login");
//配置路径拦截规则,注意,要有序
Map<String, String> map = new LinkedHashMap<>();
map.put("/doLogin", "anon");
map.put("/logout","logout");
map.put("/**", "user");
bean.setFilterChainDefinitionMap(map);
return bean;
}
/**
但是再shiro:1.6开始,新增了一个InvalidRequestFilter的过滤器,用于拦截存在安全问题的uri,不进行配置在首次访问/时,URL中会出现jsessionid,并返回400错误
* **/
@Bean
public DefaultWebSessionManager sessionManager(){
DefaultWebSessionManager sessionManager=new DefaultWebSessionManager();
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
/**设置Cookie**/
@Bean
public SimpleCookie rememberMeCookie() {
SimpleCookie cookie = new SimpleCookie("rememberMe");
cookie.setMaxAge(86400);
return cookie;
}
/**
* 设置cookie管理对象和Cookie的加密密钥,默认是AES加密
* @return
*/
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
return cookieRememberMeManager;
}
}
将logout写入filterChainDefinitionMap链中就可以自动实现注销功能,注意UserMapper类和跳转被拦截的问题即可,CookieRememberMeManager和 SimpleCookie类主要用于记住我功能后生成返回Cookie,前面三个类使@RequireRole()注解的角色控制生效。
6.前端和Controller层
- Controller层——IndexController
package com.example.shirodemo.Controller;
import com.example.shirodemo.Bean.User;
import com.example.shirodemo.config.MD5;
import com.example.shirodemo.mapper.UserMapper;
import com.example.shirodemo.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
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.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class IndexController {
@GetMapping("/login")
public String login(){
return "login";
}
@GetMapping("/")
public String index(Model model) {
model.addAttribute("message", "登录成功");
return "index";
}
@PostMapping("/doLogin")
public String doLogin(String username, String password, boolean rememberMe,Model model){
Subject subject= SecurityUtils.getSubject();
try {
String surepassword= MD5.md5(password,"Aiwin");
UsernamePasswordToken token=new UsernamePasswordToken(username, surepassword);
token.setRememberMe(rememberMe);
subject.login(token);
model.addAttribute("message","登录成功");
return "index";
} catch (AuthenticationException e) {
e.printStackTrace();
model.addAttribute("message","登录失败");
return "login";
} catch (Exception e) {
e.printStackTrace();
return "login";
}
}
}
主要是Suject类,将接受到的用户和正确的密码提交到Token,通过与Subject类进行交互进行login,MyRealm中取得的用户名和密码可以理解为就是从这里Token交上去的。
HomeController类:
package com.example.shirodemo.Controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/home")
public String home(){
return "hello";
}
@RequiresRoles("admin")
@GetMapping("/third")
public String third(){
return "third";
}
}
- 前端页面
Index.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<label style="color: red" th:text="${message}"></label>
<a th:href="@{/logout}">注销</a>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<label style="color: red" th:text="${message}"></label>
<a th:href="@{/logout}">注销</a>
<a th:href="@{/home}">跳转</a>
<a th:href="@{/third}">跳转管理员页面</a>
</body>
</html>
hello.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>第二个页面</title>
</head>
<body>
<h1>这是第二个页面</h1>
</body>
</html>
third.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>第三个页面</title>
</head>
<body>
<h1>只能管理员访问哦</h1>
</body>
</html>
至此,功能全部实现,实现效果图如下
直接关闭浏览器,然后再访问/,可以直接跳转到登录成功的页面,点击注销再访问则会跳转到登录页面,应该是通过Cookie中的rememberMe中取得user的信息进行验证判断是否通过。
~ ~ The End ~ ~
分类标签:开发,开发
文章标题:Sringboot2整合shiro实现登录认证、权限控制和记住我功能
文章链接:https://aiwin.fun/index.php/archives/2205/
最后编辑:2024 年 1 月 4 日 16:57 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
文章标题:Sringboot2整合shiro实现登录认证、权限控制和记住我功能
文章链接:https://aiwin.fun/index.php/archives/2205/
最后编辑:2024 年 1 月 4 日 16:57 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)