Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

llyJava/spring-boot-shiro

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

13 Commits

Repository files navigation

主要框架

SpringBoot version Shiro-Spring version druid version mybatis plus version shiro-redis version

Shiro架构图与基本知识

1、Shiro是Apache下的一个开源项目,我们称之为Apache Shiro。它是一个很易用与Java项目的的安全框架,提供了认证、授权、加密、会话管理,与spring Security 一样都是做一个权限的安全框架,但是与Spring Security 相比,在于 Shiro 使用了比较简单易懂易于使用的授权方式。shiro属于轻量级框架,相对于security简单的多,也没有security那么复杂。所以我这里也是简单介绍一下shiro的使用。

2、非常简单;其基本功能点如下图所示:

在这里插入图片描述

Authentication:身份认证/登录,验证用户是不是拥有相应的身份;

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

Web Support:Web支持,可以非常容易的集成到Web环境;

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

Testing:提供测试支持;

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

记住一点,Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可。

源码地址

https://github.com/MRLEILOVE/spring-boot-shiro

数据库结构

5张表,也就是现在流行的权限设计模型RBAC,建表SQL已放在项目中。

用户、角色、权限、用户-角色、角色-权限,关系如下。 在这里插入图片描述

项目结构

下面是整个项目结构,主要类已做注释。

├─main
│ ├─java
│ │ └─com
│ │ └─leigq
│ │ └─www
│ │ └─shiro
│ │ │ SpringBootShiroApplication.java
│ │ │
│ │ ├─bean
│ │ │ CacheUser.java --- 缓存用户信息
│ │ │ Response.java --- 统一返回结果
│ │ │
│ │ ├─config
│ │ │ DruidDataSourceConfig.java --- Druid数据源配置
│ │ │ DruidMonitorConfig.java --- Druid监控配置
│ │ │ MyBatisPlusConfig.java --- MyBatisPlus配置
│ │ │ MySessionManager.java --- 自定义session管理
│ │ │ MyShiroRealm.java --- 自定义 shiroRealm, 主要是重写其认证、授权
│ │ │ ShiroConfig.java --- Shiro管理
│ │ │
│ │ ├─controller
│ │ │ LoginController.java
│ │ │ PermissionController.java
│ │ │ RoleController.java
│ │ │ RolePermissionController.java
│ │ │ UserController.java
│ │ │ UserRoleController.java
│ │ │
│ │ ├─domain
│ │ │ ├─entity
│ │ │ │ Permission.java
│ │ │ │ Role.java
│ │ │ │ RolePermission.java
│ │ │ │ User.java
│ │ │ │ UserRole.java
│ │ │ │
│ │ │ └─mapper
│ │ │ PermissionMapper.java
│ │ │ RoleMapper.java
│ │ │ RolePermissionMapper.java
│ │ │ UserMapper.java
│ │ │ UserRoleMapper.java
│ │ │
│ │ ├─service
│ │ │ │ IPermissionService.java
│ │ │ │ IRolePermissionService.java
│ │ │ │ IRoleService.java
│ │ │ │ IUserRoleService.java
│ │ │ │ IUserService.java
│ │ │ │
│ │ │ └─impl
│ │ │ PermissionServiceImpl.java
│ │ │ RolePermissionServiceImpl.java
│ │ │ RoleServiceImpl.java
│ │ │ UserRoleServiceImpl.java
│ │ │ UserServiceImpl.java
│ │ │
│ │ ├─util
│ │ │ CodeGeneratorUtils.java --- MyBatisPlus代码生成器
│ │ │
│ │ └─web
│ │ │ GlobalExceptionHand.java --- 全局异常处理
│ │ │
│ │ └─exception
│ │ LoginException.java
│ │
│ └─resources
│ │ application.yml
│ │
│ ├─config
│ │ application-dev.yml
│ │ application-prod.yml
│ │ application-test.yml
│ │
│ ├─mappers
│ │ PermissionMapper.xml
│ │ RoleMapper.xml
│ │ RolePermissionMapper.xml
│ │ UserMapper.xml
│ │ UserRoleMapper.xml
│ │
│ ├─sql
│ │ shiro-V1.0.0.sql
│ │ shiro-V1.0.1.sql --- 最新版SQL
│ │
│ ├─static
│ └─templates
└─test
 └─java
 └─com
 └─leigq
 └─www
 └─shiro
 ├─base
 │ BaseApplicationTests.java
 │
 └─test
 ShiroApplicationTests.java

详细搭建过程

建议直接将代码拉下来对照着文档看

1、将最新版SQL导入数据库,SQL我已经放入项目中

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 http://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.1.6.RELEASE</version>
 <relativePath/> <!-- lookup parent from repository -->
 </parent>
 <groupId>com.leigq.www</groupId>
 <artifactId>spring-boot-shiro</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <name>spring-boot-shiro</name>
 <description>shiro demo project for Spring Boot</description>
 <properties>
 <java.version>1.8</java.version>
 <druid.version>1.0.29</druid.version>
 <commons-collections4.version>4.1</commons-collections4.version>
 <mybatis-plus.version>3.1.1</mybatis-plus.version>
 <shiro-spring.version>1.4.0</shiro-spring.version>
 <shiro-redis.version>3.1.0</shiro-redis.version>
 </properties>
 <dependencies>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-actuator</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <scope>runtime</scope>
 </dependency>
 <!-- druid数据库连接池 -->
 <dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>druid</artifactId>
 <version>${druid.version}</version>
 </dependency>
 <dependency>
 <groupId>com.baomidou</groupId>
 <artifactId>mybatis-plus-boot-starter</artifactId>
 <version>${mybatis-plus.version}</version>
 </dependency>
 <dependency>
 <groupId>com.baomidou</groupId>
 <artifactId>mybatis-plus-generator</artifactId>
 <version>${mybatis-plus.version}</version>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-freemarker</artifactId>
 </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>
 <!--编写更少量的代码:使用apache commons工具类库:
 https://www.cnblogs.com/ITtangtang/p/3966955.html-->
 <!--apache.commons.lang3-->
 <dependency>
 <groupId>org.apache.commons</groupId>
 <artifactId>commons-lang3</artifactId>
 </dependency>
 <!--你可以把这个工具看成是java.util的扩展-->
 <dependency>
 <groupId>org.apache.commons</groupId>
 <artifactId>commons-collections4</artifactId>
 <version>${commons-collections4.version}</version>
 </dependency>
 <!--apache.codec:编码方法的工具类包
 https://blog.csdn.net/u012881904/article/details/52767853-->
 <dependency>
 <groupId>commons-codec</groupId>
 <artifactId>commons-codec</artifactId>
 </dependency>
 <dependency>
 <groupId>org.apache.shiro</groupId>
 <artifactId>shiro-spring</artifactId>
 <version>${shiro-spring.version}</version>
 </dependency>
 <dependency>
 <groupId>org.crazycake</groupId>
 <artifactId>shiro-redis</artifactId>
 <version>${shiro-redis.version}</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>
 </plugin>
 </plugins>
 </build>
</project>

3、编辑application.yml

我项目中使用了多环境配置,你们可根据自己情况修改

mybatis-plus:
 configuration:
 map-underscore-to-camel-case: true
 use-generated-keys: true
 mapper-locations: classpath*:/mappers/**/*.xml
 type-aliases-package: com.leigq.www.shiro.domain.entity
server:
 tomcat:
 uri-encoding: UTF-8
spring:
 datasource:
 connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;config.decrypt=true;config.decrypt.key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKPYsCl3alwZlRb1vKoFdVu0LP3Nm/+vH5iOWxI83pkUbrQc13Lxz/VT3D+H+ziaUpUsA+ZjG4iZGTDJWZnP8kcCAwEAAQ==
 driver-class-name: com.mysql.cj.jdbc.Driver
 filters: config,stat,wall,slf4j
 initialSize: 5
 maxActive: 20
 maxPoolPreparedStatementPerConnectionSize: 20
 maxWait: 60000
 minEvictableIdleTimeMillis: 300000
 minIdle: 5
 password: kGJF6c+pzVsf49LGs01ss0yijBGXIpNEp20cMkNCQo3ONaeMNPeoW9M89v+nGeiWs95/D2Ms59uGyydDGUWpmg==
 poolPreparedStatements: true
 testOnBorrow: false
 testOnReturn: false
 testWhileIdle: true
 timeBetweenEvictionRunsMillis: 60000
 type: com.alibaba.druid.pool.DruidDataSource
 url: jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=true&serverTimezone=UTC
 username: root
 validationQuery: SELECT 1 FROM DUAL
 thymeleaf:
 cache: false
 redis:
 host: localhost
 port: 6379
 timeout: 2000s
 password: 111111

4、创建MySessionManager

package com.leigq.www.shiro.config;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
/**
 * 自定义session管理
 * <br/>
 * 传统结构项目中,shiro从cookie中读取sessionId以此来维持会话,在前后端分离的项目中(也可在移动APP项目使用),
 * 我们选择在ajax的请求头中传递sessionId,因此需要重写shiro获取sessionId的方式。
 * 自定义MySessionManager类继承DefaultWebSessionManager类,重写getSessionId方法
 * @author :leigq
 * @date :2019年7月1日 10:52
 */
public class MySessionManager extends DefaultWebSessionManager {
 private static final String AUTHORIZATION = "Authorization";
 private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
 @Override
 protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
 String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
 //如果请求头中有 Authorization 则其值为sessionId
 if (!StringUtils.isEmpty(id)) {
 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
 return id;
 } else {
 //否则按默认规则从cookie取sessionId
 return super.getSessionId(request, response);
 }
 }
}

5、创建MyShiroRealm

package com.leigq.www.shiro.config;
import com.leigq.www.shiro.domain.entity.Permission;
import com.leigq.www.shiro.domain.entity.Role;
import com.leigq.www.shiro.domain.entity.User;
import com.leigq.www.shiro.service.IPermissionService;
import com.leigq.www.shiro.service.IRoleService;
import com.leigq.www.shiro.service.IUserService;
import lombok.extern.slf4j.Slf4j;
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 javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
/**
 * @author :leigq
 * @date :2019年6月28日 16:31
 * @description:自定义 shiroRealm, 主要是重写其认证、授权
 */
@Slf4j
public class MyShiroRealm extends AuthorizingRealm {
 @Resource
 private IUserService iUserService;
 @Resource
 private IRoleService iRoleService;
 @Resource
 private IPermissionService iPermissionService;
 /**
 * create by: leigq
 * description: 授权
 * create time: 2019年7月1日 10:32
 *
 * @return 权限信息,包括角色以及权限
 */
 @Override
 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
 log.warn("开始执行授权操作.......");
 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
 //如果身份认证的时候没有传入User对象,这里只能取到userName
 //也就是SimpleAuthenticationInfo构造的时候第一个参数传递需要User对象
 User user = (User) principals.getPrimaryPrincipal();
 // 查询用户角色,一个用户可能有多个角色
 List<Role> roles = iRoleService.getUserRoles(user.getUserId());
 for (Role role : roles) {
 authorizationInfo.addRole(role.getRole());
 // 根据角色查询权限
 List<Permission> permissions = iPermissionService.getRolePermissions(role.getRoleId());
 for (Permission p : permissions) {
 authorizationInfo.addStringPermission(p.getPermission());
 }
 }
 return authorizationInfo;
 }
 /**
 * create by: leigq
 * description: 主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。
 * create time: 2019年7月1日 09:04
 *
 * @return 身份验证信息
 */
 @Override
 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
 log.warn("开始进行身份认证......");
 //获取用户的输入的账号.
 String userName = (String) token.getPrincipal();
 //通过username从数据库中查找 User对象.
 //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
 User user = iUserService.findByUsername(userName);
 if (Objects.isNull(user)) {
 return null;
 }
 return new SimpleAuthenticationInfo(
 // 这里传入的是user对象,比对的是用户名,直接传入用户名也没错,但是在授权部分就需要自己重新从数据库里取权限
 user,
 // 密码
 user.getPassword(),
 // salt = username + salt
 ByteSource.Util.bytes(user.getCredentialsSalt()),
 // realm name
 getName()
 );
 }
}

6、创建ShiroConfig

package com.leigq.www.shiro.config;
import lombok.Data;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
 * @author :leigq
 * @date :2019年6月28日 16:53
 * @description:shiro配置
 */
@Configuration
@ConfigurationProperties(
 prefix = "spring.redis"
)
@Data
public class ShiroConfig {
 private String host = "localhost";
 private int port = 6379;
 private String password;
 private Duration timeout;
 /**
 * Filter工厂,设置对应的过滤条件和跳转条件
 * create by: leigq
 * create time: 2019年7月3日 14:29
 *
 * @return ShiroFilterFactoryBean
 */
 @Bean
 public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
 shiroFilterFactoryBean.setSecurityManager(securityManager);
 // 过滤器链定义映射
 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
 /*
 * anon:所有url都都可以匿名访问,authc:所有url都必须认证通过才可以访问;
 * 过滤链定义,从上向下顺序执行,authc 应放在 anon 下面
 * */
 filterChainDefinitionMap.put("/login", "anon");
 // 配置不会被拦截的链接 顺序判断,因为前端模板采用了thymeleaf,这里不能直接使用 ("/static/**", "anon")来配置匿名访问,必须配置到每个静态目录
 filterChainDefinitionMap.put("/css/**", "anon");
 filterChainDefinitionMap.put("/fonts/**", "anon");
 filterChainDefinitionMap.put("/img/**", "anon");
 filterChainDefinitionMap.put("/js/**", "anon");
 filterChainDefinitionMap.put("/html/**", "anon");
 // 所有url都必须认证通过才可以访问
 filterChainDefinitionMap.put("/**", "authc");
 // 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了, 位置放在 anon、authc下面
 filterChainDefinitionMap.put("/logout", "logout");
 // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
 // 配器shirot认登录累面地址,前后端分离中登录累面跳转应由前端路由控制,后台仅返回json数据, 对应LoginController中unauth请求
 shiroFilterFactoryBean.setLoginUrl("/un_auth");
 // 登录成功后要跳转的链接, 此项目是前后端分离,故此行注释掉,登录成功之后返回用户基本信息及token给前端
 // shiroFilterFactoryBean.setSuccessUrl("/index");
 // 未授权界面, 对应LoginController中 unauthorized 请求
 shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
 return shiroFilterFactoryBean;
 }
 /**
 * 凭证匹配器(由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了)
 * create by: leigq
 * create time: 2019年7月3日 14:30
 *
 * @return HashedCredentialsMatcher
 */
 @Bean
 public HashedCredentialsMatcher hashedCredentialsMatcher() {
 HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
 // 散列算法:这里使用MD5算法;
 hashedCredentialsMatcher.setHashAlgorithmName("md5");
 // 散列的次数,比如散列两次,相当于 md5(md5(""));
 hashedCredentialsMatcher.setHashIterations(2);
 return hashedCredentialsMatcher;
 }
 /**
 * 将自己的验证方式加入容器
 * create by: leigq
 * create time: 2019年7月3日 14:30
 *
 * @return MyShiroRealm
 */
 @Bean
 public MyShiroRealm myShiroRealm() {
 MyShiroRealm myShiroRealm = new MyShiroRealm();
 myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
 return myShiroRealm;
 }
 /**
 * RedisSessionDAO shiro sessionDao层的实现 通过redis, 使用的是shiro-redis开源插件
 * create by: leigq
 * create time: 2019年7月3日 14:30
 *
 * @return RedisSessionDAO
 */
 @Bean
 public RedisSessionDAO redisSessionDAO() {
 RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
 redisSessionDAO.setRedisManager(redisManager());
 redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
 redisSessionDAO.setExpire(1800);
 return redisSessionDAO;
 }
 /**
 * Session ID 生成器
 * <br/>
 * create by: leigq
 * <br/>
 * create time: 2019年7月3日 16:08
 *
 * @return JavaUuidSessionIdGenerator
 */
 @Bean
 public JavaUuidSessionIdGenerator sessionIdGenerator() {
 return new JavaUuidSessionIdGenerator();
 }
 /**
 * 自定义sessionManager
 * create by: leigq
 * create time: 2019年7月3日 14:31
 *
 * @return SessionManager
 */
 @Bean
 public SessionManager sessionManager() {
 MySessionManager mySessionManager = new MySessionManager();
 mySessionManager.setSessionDAO(redisSessionDAO());
 return mySessionManager;
 }
 /**
 * 配置shiro redisManager, 使用的是shiro-redis开源插件
 * <br/>
 * create by: leigq
 * <br/>
 * create time: 2019年7月3日 14:33
 *
 * @return RedisManager
 */
 private RedisManager redisManager() {
 RedisManager redisManager = new RedisManager();
 redisManager.setHost(host);
 redisManager.setPort(port);
 redisManager.setTimeout((int) timeout.toMillis());
 redisManager.setPassword(password);
 return redisManager;
 }
 /**
 * cacheManager 缓存 redis实现, 使用的是shiro-redis开源插件
 * <br/>
 * create by: leigq
 * <br/>
 * create time: 2019年7月3日 14:33
 *
 * @return RedisCacheManager
 */
 @Bean
 public RedisCacheManager cacheManager() {
 RedisCacheManager redisCacheManager = new RedisCacheManager();
 redisCacheManager.setRedisManager(redisManager());
 // 必须要设置主键名称,shiro-redis 插件用过这个缓存用户信息
 redisCacheManager.setPrincipalIdFieldName("userId");
 return redisCacheManager;
 }
 /**
 * create by: leigq
 * description: 权限管理,配置主要是Realm的管理认证
 * create time: 2019年7月1日 10:09
 *
 * @return SecurityManager
 */
 @Bean
 public SecurityManager securityManager() {
 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
 securityManager.setRealm(myShiroRealm());
 // 自定义session管理 使用redis
 securityManager.setSessionManager(sessionManager());
 // 自定义缓存实现 使用redis
 securityManager.setCacheManager(cacheManager());
 return securityManager;
 }
 /*
 * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
 * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
 */
 @Bean
 public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
 DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
 advisorAutoProxyCreator.setProxyTargetClass(true);
 return advisorAutoProxyCreator;
 }
 @Bean
 public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
 AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
 authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
 return authorizationAttributeSourceAdvisor;
 }
 @Bean
 public SimpleCookie cookie() {
 // cookie的name,对应的默认是 JSESSIONID
 SimpleCookie cookie = new SimpleCookie("SHARE_JSESSIONID");
 cookie.setHttpOnly(true);
 // path为 / 用于多个系统共享 JSESSIONID
 cookie.setPath("/");
 return cookie;
 }
 /* 此项目使用 shiro 场景为前后端分离项目,这里先注释掉,统一异常处理已在 GlobalExceptionHand.java 中实现 */
 /**
 * create by: leigq
 * description: 异常处理, 详见:https://www.cnblogs.com/libra0920/p/6289848.html
 * create time: 2019年7月1日 10:28
 * @return SimpleMappingExceptionResolver
 */
// @Bean(name = "simpleMappingExceptionResolver")
// public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() {
// SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
// Properties mappings = new Properties();
// mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
// mappings.setProperty("UnauthorizedException", "/user/403");
// r.setExceptionMappings(mappings); // None by default
// r.setDefaultErrorView("error"); // No default
// r.setExceptionAttribute("exception"); // Default is "exception"
// //r.setWarnLogCategory("example.MvcLogger"); // No default
// return r;
// }
}

7、创建LoginController

package com.leigq.www.shiro.controller;
import com.leigq.www.shiro.bean.CacheUser;
import com.leigq.www.shiro.bean.Response;
import com.leigq.www.shiro.domain.entity.User;
import com.leigq.www.shiro.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
 * @author :leigq
 * @date :2019年6月28日 16:55
 * @description:登录Controller
 */
@Slf4j
@RestController
public class LoginController {
 @Resource
 private IUserService iUserService;
 @Resource
 private Response response;
 /**
 * create by: leigq
 * description: 登录
 * create time: 2019年6月28日 17:11
 *
 * @return 登录结果
 */
 @PostMapping("/login")
 public Response login(User user) {
 log.warn("进入登录.....");
 String userName = user.getUserName();
 String password = user.getPassword();
 if (StringUtils.isBlank(userName)) {
 return response.failure("用户名为空!");
 }
 if (StringUtils.isBlank(password)) {
 return response.failure("密码为空!");
 }
 CacheUser loginUser = iUserService.login(userName, password);
 // 登录成功返回用户信息
 return response.success("登录成功!", loginUser);
 }
 /**
 * create by: leigq
 * description: 登出
 * create time: 2019年6月28日 17:37
 */
 @GetMapping("/logout")
 public Response logOut() {
 iUserService.logout();
 return response.success("登出成功!");
 }
 /**
 * 未登录,shiro应重定向到登录界面,此处返回未登录状态信息由前端控制跳转页面
 * <br/>
 * create by: leigq
 * <br/>
 * create time: 2019年7月3日 14:53
 * @return 
 */
 @RequestMapping("/un_auth")
 public Response unAuth() {
 return response.failure(HttpStatus.UNAUTHORIZED, "用户未登录!", null);
 }
 /**
 * 未授权,无权限,此处返回未授权状态信息由前端控制跳转页面
 * <br/>
 * create by: leigq
 * <br/>
 * create time: 2019年7月3日 14:53
 * @return
 */
 @RequestMapping("/unauthorized")
 public Response unauthorized() {
 return response.failure(HttpStatus.FORBIDDEN, "用户无权限!", null);
 }
}

8、具体登录方法

package com.leigq.www.shiro.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.leigq.www.shiro.bean.CacheUser;
import com.leigq.www.shiro.domain.entity.User;
import com.leigq.www.shiro.domain.mapper.UserMapper;
import com.leigq.www.shiro.service.IUserService;
import com.leigq.www.shiro.web.exception.LoginException;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
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.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author leigq
 * @since 2019年06月28日
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
 @Override
 public User findByUsername(String username) {
 return baseMapper.selectOne(
 new LambdaQueryWrapper<User>().eq(User::getUserName, username)
 );
 }
 @Override
 public CacheUser login(String userName, String password) {
 // 获取Subject实例对象,用户实例
 Subject currentUser = SecurityUtils.getSubject();
 // 将用户名和密码封装到UsernamePasswordToken
 UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
 CacheUser cacheUser;
 // 4、认证
 try {
 // 传到 MyShiroRealm 类中的方法进行认证
 currentUser.login(token);
 // 构建缓存用户信息返回给前端
 User user = (User) currentUser.getPrincipals().getPrimaryPrincipal();
 cacheUser = CacheUser.builder()
 .token(currentUser.getSession().getId().toString())
 .build();
 BeanUtils.copyProperties(user, cacheUser);
 log.warn("CacheUser is {}", cacheUser.toString());
 } catch (UnknownAccountException e) {
 log.error("账户不存在异常:", e);
 throw new LoginException("账号不存在!", e);
 } catch (IncorrectCredentialsException e) {
 log.error("凭据错误(密码错误)异常:", e);
 throw new LoginException("密码不正确!", e);
 } catch (AuthenticationException e) {
 log.error("身份验证异常:", e);
 throw new LoginException("用户验证失败!", e);
 }
 return cacheUser;
 }
 @Override
 public void logout() {
 Subject subject = SecurityUtils.getSubject();
 subject.logout();
 }
 @Override
 public List<User> listUsers() {
 return baseMapper.selectList(new LambdaQueryWrapper<>());
 }
}

上面我列出了项目中主要的几个类,大家可以对照着项目看,每个类中的注释已经写的很详细了。

使用及测试

我们配置每个接口的权限使用@RequiresPermissions("user:view")注解即可,其中user:view对应权限表中的权限。

在这里插入图片描述

1、登录测试

在这里插入图片描述

在这里插入图片描述

登录成功会将用户信息存入缓存。

2、请求查询用户接口

我们先输入错误的token试试

在这里插入图片描述

我们再输入正确的token试试 在这里插入图片描述

3、请求用户删除接口

在这里插入图片描述

因为我们没有给此用户配置此权限,所以返回无权限

4、退出登录

在这里插入图片描述

我们再请求用户列表接口

在这里插入图片描述

感谢

About

spring-boot-shiro前后端分离实现

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 88.0%
  • TSQL 12.0%

AltStyle によって変換されたページ (->オリジナル) /