Shiro与SpringBoot整合,实现登录拦截、用户认证、用户授权等。实战demo
一、开发环境名称版本IntelliJ IDEA2019.3.5 x64JDK1.8MySQLmysql-5.7.31-winx64SpringBoot2.0+Mavenapache-maven-3.6.3shiro1 .4.0二、项目搭建1、创建一个SpringBoot项目导入相关pom依赖:<parent><groupId>org.springframework.boot
文章目录
开篇必读:
shiro这个框架虽然学习难度不高,但是底层封装很深,一篇博客也讲不明白,这东西估计得再写几篇文章,这篇文章主要是搭建一下shiro的项目架构,有shiro的基础功能。后期要添加的东西也不少,以这篇博客搭建的项目为基础,要想学shiro得一段时间,不是说你懂了就是你会了,你得动手写代码。键盘敲不烂,薪资不过万。后面继续更新,全部看完咱们不敢保证你一定会学会shiro,但一定会有收获。
坚持!
一、开发环境
名称 | 版本 |
---|---|
IntelliJ IDEA | 2019.3.5 x64 |
JDK | 1.8 |
MySQL | mysql-5.7.31-winx64 |
SpringBoot | 2.0+ |
Maven | apache-maven-3.6.3 |
shiro | 1 .4.0 |
MyBatis | 2.1.0 |
二、项目搭建
1、创建一个SpringBoot项目导入相关pom依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/><!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.1.3.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<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>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
</project>
2、在 resources 中创建 application.yml 配置文件。
详细配置如下:
如果你的idea是老版本可能因为系统找不到MyBatis的mapper目录而报错,你可以在resource 目录下创建mapper文件夹,也可以将 yml 配置文件中与MyBatis相关的配置暂时关掉 (注释掉的意思,配置文件中注释使用#号)。
server:
port: 8080
spring:
application:
name: shiro-springboot
datasource:
url: jdbc:mysql://localhost:3306/hibernate
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.xa.DruidXADataSource
thymeleaf:
cache: false
mvc:
static-path-pattern: /templates/user/**
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.zkr.mingyu.entity
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
level:
com.example.demo.dao : debug
SpringBoot开启热部署方法如下:见我另一篇博客介绍。
飞机票:SpringBoot 项目如何开启热部署
3、创建 SpringBoot 启动器 Application.class:
启动类源码:
package com.zkr.mingyu;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
测试项目是否可以正常启动,访问 localhost:8080 端口,查看是否运行正常。
三、配置 shiro 配置类
目前的项目目录:
编写配置类与 SpringBoot 进行整合。
配置类内容具体如下:
从底层往上描述:
如果页面中不使用shiro标签,也可以不写Shiro与Thymeleaf整合配置类。
- 自定义Realm。
- 创建 安全管理器 DefaultWebSecuityManager。
- 创建 ShiroFilterFactoryBean
- Shiro与Thymeleaf整合配置类。为了页面支持shiro标签)。
全文最重要最关键的两个点,一个是自定义 Realm 以及shiro 的配置类了。全文核心
1、自定义 Realm 类。
(1)创建 MyRealm 类,继承 AuthorizingRealm 类,必须继承。
(2)实现 doGetAuthorizationInfo、doGetAuthenticationInfo 方法。
要实现登录,doGetAuthenticationInfo 是核心,账号密码在doGetAuthenticationInfo() 方法中做校验。
而授权操作则在 doGetAuthorizationInfo() 方法中操作,比如页面资源限制,用户可以访问哪个资源,执行哪些操作等。都在这个方法中配置。
package com.zkr.mingyu.shiro;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.io.Serializable;
public class MyRealm extends AuthorizingRealm implements Serializable {
/**
* 授权
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("执行授权方法: doGetAuthorizationInfo");
return null;
}
/**
* 认证
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行认证方法: doGetAuthenticationInfo");
return null;
}
}
2、创建shiro配置类。
这个配置类很重要,仔细读一下,一环扣一环。后期密码加密(MD5 + salt + 散列)都在这个类中配置,以及与 Thymeleaf 整合配置等等。
package com.zkr.mingyu.config;
import com.zkr.mingyu.shiro.MyRealm;
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;
/**标注 @Configuration 注解,
* 标注这是一个配置类,
* 让项目启动时加载该配置类。
*/
@Configuration
public class ShiroConfig {
/**
* 创建ShiroFilterFactory
* 设置权限规则 需要注入securityManage
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
/**
* 创建安全管理器,
* 并为 securityManager 注入自定义的 Realm 类
* @param realm
* @return
*/
@Bean
public DefaultWebSecurityManager getSecurityManager(MyRealm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
/**
* 配置自定义 Realm 类
* @Bean 将 MyRealm 注入到 Spring 容器当中
* @return
*/
@Bean
public MyRealm getMyRealm(){
return new MyRealm();
}
}
四、创建页面、Controller、 Service等。
1、在 resource 目录下创建 templeates 文件夹,用来存放 html 页面。并创建index、 login 页面。
index:
login:
2、Controller、 Service、 dao 等
现在的目录结构:
Controller:
package com.zkr.mingyu.controller;
import com.zkr.mingyu.entity.User;
import com.zkr.mingyu.service.UserService;
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 javax.annotation.Resource;
@Controller
public class UserController {
@Resource
private UserService userService;
@RequestMapping("/index")
public String login(User user, Model model) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
try {
subject.login(token);
return "index";
} catch (UnknownAccountException e) {
model.addAttribute("msg", "用户名错误!");
return "login";
} catch (IncorrectCredentialsException e) {
model.addAttribute("msg", "密码错误!");
return "login";
}
}
@RequestMapping("/login")
public String index() {
return "login";
}
@RequestMapping("/loginOut")
public String loginOut() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "login";
}
@RequestMapping("/update")
public String update() {
return "/user/update";
}
}
Service:
package com.zkr.mingyu.service;
import com.zkr.mingyu.entity.User;
import org.apache.ibatis.annotations.Param;
public interface UserService {
/**
* 根据用户名查找用户
* @param userName
* @return
*/
User findByUserName(@Param("username") String userName);
}
ServiceImpl:
package com.zkr.mingyu.service;
import com.zkr.mingyu.dao.UserMapper;
import com.zkr.mingyu.entity.User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
@Service
@Transactional //开启事务
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
public User findByUserName(String userName) {
return userMapper.findByUserName(userName);
}
}
Dao:
package com.zkr.mingyu.dao;
import com.zkr.mingyu.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface UserMapper {
/**
* 根据用户名查找用户
* @param userName
* @return
*/
User findByUserName(@Param("username") String userName);
}
entity:
package com.zkr.mingyu.entity;
import java.io.Serializable;
public class User implements Serializable {
/**
* id
*/
private Integer id;
/**
* 账号
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 权限
*/
private String auth;
/**
* 随机盐
*/
private String salt;
public User() {
}
public User(Integer id, String username, String password, String auth, String salt) {
this.id = id;
this.username = username;
this.password = password;
this.auth = auth;
this.salt = salt;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getAuth() {
return auth;
}
public void setAuth(String auth) {
this.auth = auth;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", auth='" + auth + '\'' +
", salt='" + salt + '\'' +
'}';
}
}
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.zkr.mingyu.dao.UserMapper">
<select id="findByUserName" parameterType="String" resultType="user">
select id, username, password from user where username = #{username}
</select>
</mapper>
五、修改自定义 MyRealm
自定义 MyRealm 类中 doGetAuthenticationInfo()方法主要配置用户认证。
doGetAuthorizationInfo()方法主要配置用户权限配置,说白了也就是给这个用户赋予什么角色、都有哪些权限。以及对某些资源是否可以访问,如果可以访问,那么该用户可以对这个资源执行哪些操作。操作 == CRUD呗。
package com.zkr.mingyu.shiro;
import com.zkr.mingyu.entity.User;
import com.zkr.mingyu.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.io.Serializable;
public class MyRealm extends AuthorizingRealm implements Serializable {
@Autowired
private UserService userService;
/**
* 执行授权逻辑
* 权限要和资源对应
* 权限声明该用户可以访问系统中哪些资源,对系统中哪些资源进行操作
* 不同的用户,拥有不同的权限
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("执行授权方法: doGetAuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
User user = (User) principals.getPrimaryPrincipal();
User byUserName = userService.findByUserName(user.getUsername());
/**
* 这个方法中为授权操作
* 基本常用方法有:
*/
/**
* 控制台打印结果:
* getRoles: null
* getObjectPermissions: null
* getStringPermissions: [user:add]
* getClass: class org.apache.shiro.authz.SimpleAuthorizationInfo
*/
//获取用户角色
/* System.out.println("getRoles: " + info.getRoles());
System.out.println("getObjectPermissions: " + info.getObjectPermissions());
//获取用户权限
System.out.println("getStringPermissions: " + info.getStringPermissions());
System.out.println("getClass: " + info.getClass());*/
return null;
}
/**
* 认证
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行认证方法: doGetAuthenticationInfo");
String username = (String) token.getPrincipal();
User user = userService.findByUserName(username);
if(user == null){
return null;
}
return new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
}
}
这是我写的另一个 MyRealm 配置,简单看看了解一下就可。后期还会继续更新,这篇博客开篇也说了,是搭建项目基本框架。后期后在文末添加新的博客链接,最近有时间抓紧谢谢,分享这份知识。
六、配置资源访问权限 ShiroConfig
ShiroFilterFactoryBean 方法主要配置某些资源需要哪些权限才能访问。以及配置默认的登录页面的 URL 地址。访问某些权限不足的资源跳转到哪个页面,都在这里配置。
DefaultWebSecurityManager 是一个安全管理器,后期我们要做密码加密操作(密码 + 盐 + MD5 + 哈希散列)的时候,我们就要在 DefaultWebSecurityManager() 方法中配置自定义的密码 管理器。
package com.zkr.mingyu.config;
import com.zkr.mingyu.shiro.MyRealm;
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 java.util.LinkedHashMap;
import java.util.Map;
/**标注 @Configuration 注解,
* 标注这是一个配置类,
* 让项目启动时加载该配置类。
*/
@Configuration
public class ShiroConfig {
/**
* 创建ShiroFilterFactory
* 设置权限规则 需要注入securityManage
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
/**
* Shiro内置过滤器,可实现权限相关的拦截器
* 常用的过滤器:
* anon: 无需认证(登录) 可以访问
* authc: 必须认证才可以访问
* user:如果使用rememberMe的功能可以直接访问
* perms: 该资源必须得到资源权限才可以访问
* role: 该资源必须得到角色权限才可以访问
*/
Map<String, String> filterMap = new LinkedHashMap<String,String>();
filterMap.put("/login","anon");
filterMap.put("/index","anon");
filterMap.put("/*","authc");
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
/**
* 创建安全管理器,
* 并为 securityManager 注入自定义的 Realm 类
* @param realm
* @return
*/
@Bean
public DefaultWebSecurityManager getSecurityManager(MyRealm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
/**
* 配置自定义 Realm 类
* @Bean 将 MyRealm 注入到 Spring 容器当中
* @return
*/
@Bean
public MyRealm getMyRealm(){
return new MyRealm();
}
}
代码不多,简单写个 demo 后期一步一步完善,有时间继续更新新的文章。比如密码加密、资源权限细分、会话、缓存等。
后期写好了新的功能,我会放在文章头、文章尾附上链接。看完点个赞,你的赞就是我更新的动力!收藏文章,后期在文章头部有新博客的链接。
附上一句话 (共勉):
滚水看不到倒影,盛怒看不到真相。
更多推荐
所有评论(0)