springboot整合shiro完成认证功能
一、准备阶段🥝
1.创建springboot工程🍓
2.引入依赖🍓
<?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.example</groupId>
<artifactId>shiro-springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shiro-springboot</name>
<description>shiro-springboot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!--shiro和redis整合的依赖-->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.4.6</version>
</dependency>
<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.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--shiro和springboot整合的依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--shrio和thymeleaf集成的扩展依赖,为了能在页面上使用xsln:shrio的标签-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</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.配置文件🍓
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql:///db2?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
#??
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.mapper-locations=classpath:/mapper/*.xml
#thymeleaf??????
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
shiro.hashAlgorithmName=MD5
shiro.hashIterations=1024
4.创建工程结构🍓
二、编码阶段🥝
1.config层🍓
该层代码用于将需要交给ioc管理的类进行处理
ShiroConfig
package com.lzq.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.lzq.realm.MyRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
securityManager.setRealm(myRealm());
securityManager.setCacheManager(redisCacheManager());
return securityManager;
}
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis
* 使用的是shiro-redis开源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
@Bean
public MyRealm myRealm(){
MyRealm myRealm=new MyRealm();
//设置密码加密器
myRealm.setCredentialsMatcher(credentialsMatcher());
return myRealm;
}
@Value("${shiro.hashAlgorithmName}")
private String hashAlgorithmName;
@Value("${shiro.hashIterations}")
private int hashIterations;
@Bean
public HashedCredentialsMatcher credentialsMatcher(){
HashedCredentialsMatcher credentialsMatcher=new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName(hashAlgorithmName);
credentialsMatcher.setHashIterations(hashIterations);
return credentialsMatcher;
}
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean factoryBean(){
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
shiroFilterFactoryBean.setLoginUrl("/login.html");
//设置过滤规则
Map<String,String> map=new HashMap<>();
map.put("/login","anon");
map.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
//springboot如何注册web三大组件。
@Bean
public FilterRegistrationBean<Filter> filterRegistrationBean(){
FilterRegistrationBean<Filter> filterRegistrationBean=new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new DelegatingFilterProxy());
filterRegistrationBean.setName("shiroFilter");
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
/**
* 这里是为了能在html页面引用shiro标签,
*/
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
@Bean
public RedisCacheManager redisCacheManager(){
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
@Bean
public RedisManager redisManager(){
RedisManager redisManager = new RedisManager();
redisManager.setHost("192.168.179.129");
redisManager.setDatabase(1);
return redisManager;
}
}
2.controller层🍓
package com.lzq.controller;
import com.lzq.vo.LoginVo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class LoginController {
@PostMapping("/login")
public String login(LoginVo loginVo){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(loginVo.getUsername(),loginVo.getPassword());
try {
subject.login(token);
System.out.println("登录成功");
return "success";
}catch (Exception e){
e.printStackTrace();
System.out.println("登录失败");
return "redirect:/login.jsp";
}
}
}
3.dao层🍓
package com.lzq.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lzq.pojo.Permission;
import java.util.List;
public interface PermissionDao extends BaseMapper<Permission> {
List<Permission> selectPermissionByUserid(Integer userid);
}
package com.lzq.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lzq.pojo.User;
public interface UserDao extends BaseMapper<User> {
}
4.pojo层🍓
package com.lzq.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Permission {
@TableId(type = IdType.AUTO)
private Integer perid;
private String pername;
private String percode;
}
package com.lzq.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
//当我们数据库中主键字段名不是id时 必须加@tableid注解
private Integer userid;
private String username;
private String userpwd;
private String sex;
private String address;
private String salt;
}
5.realm层🍓
package com.lzq.realm;
import com.lzq.pojo.User;
import com.lzq.service.PermissionService;
import com.lzq.service.UserService;
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.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private PermissionService permissionService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
User user = (User) principals.getPrimaryPrincipal();
List<String> list = permissionService.selectPermissionByUserid(user.getUserid());
if (list!=null){
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(list);
return info;
}
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = token.getPrincipal().toString();
User user = userService.findAllUsername(username);
if (user!=null){
//获取到盐管理对象
ByteSource source = ByteSource.Util.bytes(user.getSalt());
//将所需要的数据交给shiro
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getUserpwd(),source,this.getName());
return info;
}
return null;
}
}
6.service层🍓
package com.lzq.service;
import com.lzq.pojo.Permission;
import com.lzq.pojo.User;
import java.util.List;
public interface PermissionService {
List<String> selectPermissionByUserid(Integer userid);
}
package com.lzq.service;
import com.lzq.pojo.User;
public interface UserService {
User findAllUsername(String username);
}
package com.lzq.service.impl;
import com.lzq.dao.PermissionDao;
import com.lzq.pojo.Permission;
import com.lzq.service.PermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class PermissionServiceImpl implements PermissionService {
@Autowired
private PermissionDao permissionDao;
@Override
public List<String> selectPermissionByUserid(Integer userid) {
List<Permission> permissions = permissionDao.selectPermissionByUserid(userid);
List<String> collect = permissions.stream().map(Permission::getPercode).collect(Collectors.toList());
return collect;
}
}
package com.lzq.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lzq.dao.UserDao;
import com.lzq.pojo.User;
import com.lzq.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public User findAllUsername(String username) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username",username);
User user = userDao.selectOne(wrapper);
return user;
}
}
7.vo层🍓
package com.lzq.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginVo {
private String username;
private String password;
}
8. mapper层🍓
<?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.lzq.dao.PermissionDao">
<select id="selectPermissionByUserid" resultType="com.lzq.pojo.Permission">
select p.* from user_role ur join role_permission rp on ur.roleid=rp.roleid
join permission p on rp.perid=p.perid where ur.userid=#{userid}
</select>
</mapper>
9.前端页面🍓
登录页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="/login" method="post">
账号:<input type="text" name="username"/><br><br>
密码:<input type="text" name="password"/><br><br>
<input type="submit" value="登录"/>
</form>
</body>
</html>
登录成功页面