使用shiro框架实现认证与授权
文章目录
登录功能涉及的5张数据表
1)用户表
2)角色表
3)菜单表
4)用户角色关系表
5)角色菜单关系表
SpringBoot配置(application.yml)
server:
session-timeout: 1800
# tomcat:
# max-threads: 1000
# min-spare-threads: 30
port: 8080
# uri-encoding: utf-8
#security:
# basic:
# enabled: false
spring:
thymeleaf:
mode: LEGACYHTML5
cache: false
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
profiles:
active: dev
servlet:
multipart:
max-file-size: 30MB
max-request-size: 30MB
devtools:
restart:
enabled: true
cache:
type: ehcache
ehcache:
config: classpath:config/ehcache.xml
mybatis:
configuration:
map-underscore-to-camel-case: true
mapper-locations: mybatis/**/*Mapper.xml
typeAliasesPackage: com.yangzc.studentboot.**.domain
#[弃用]配置缓存和session存储方式,默认ehcache,可选redis,[弃用]调整至 spring cache type【shiro.用户,权限,session,spring.cache通用】
#[弃用]cacheType: ehcache
开发环境配置(application-dev.yml)
studentboot:
uploadPath: d:/upload/
username: admin
password: 111111
logging:
level:
root: info
com.yangzc.studentboot: debug
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/studentboot?useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: 123456
initialSize: 1
minIdle: 3
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 30000
validationQuery: select 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,slf4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
#useGlobalDataSourceStat: true
依赖配置
<?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.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yangzc</groupId>
<artifactId>studentboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>studentboot</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<mysql.version>5.1.41</mysql.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!--druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!--commons -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<!--shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!-- shiro ehcache -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<exclusions>
<exclusion>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
</exclusion>
</exclusions>
<version>1.4.0</version>
</dependency>
<!-- ehchache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
</plugin>
</plugins>
</build>
</project>
shiro配置
package com.yangzc.studentboot.common.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.yangzc.studentboot.login.UserRealm;
import net.sf.ehcache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
import org.apache.shiro.session.mgt.eis.SessionDAO;
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.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
/**
* @author yangzc
*/
@Configuration
public class ShiroConfig {
@Value("${spring.cache.type}")
private String cacheType ;
@Value("${server.session-timeout}")
private int tomcatTimeout;
@Bean
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* ShiroDialect,为了在thymeleaf里使用shiro的标签的bean
*
* @return
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
@Bean
ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/index");
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/login","anon");
filterChainDefinitionMap.put("/getVerify","anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/docs/**", "anon");
filterChainDefinitionMap.put("/druid/**", "anon");
filterChainDefinitionMap.put("/upload/**", "anon");
filterChainDefinitionMap.put("/files/**", "anon");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/blog", "anon");
filterChainDefinitionMap.put("/blog/open/**", "anon");
filterChainDefinitionMap.put("/**/*.js", "anon");
filterChainDefinitionMap.put("/**/*.css", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm.
securityManager.setRealm(userRealm());
securityManager.setCacheManager(ehCacheManager());
securityManager.setSessionManager(sessionManager());
return securityManager;
}
@Bean
UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
return userRealm;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public SessionDAO sessionDAO() {
return new MemorySessionDAO();
}
/**
* shiro session的管理
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(tomcatTimeout * 1000);
sessionManager.setSessionDAO(sessionDAO());
Collection<SessionListener> listeners = new ArrayList<SessionListener>();
listeners.add(new BDSessionListener());
sessionManager.setSessionListeners(listeners);
return sessionManager;
}
@Bean
public EhCacheManager ehCacheManager() {
EhCacheManager em = new EhCacheManager();
em.setCacheManager(cacheManager());
return em;
}
@Bean("cacheManager2")
CacheManager cacheManager(){
return CacheManager.create();
}
}
认证与授权
package com.yangzc.studentboot.login;
import com.yangzc.studentboot.common.config.ApplicationContextRegister;
import com.yangzc.studentboot.common.utils.ShiroUtils;
import com.yangzc.studentboot.system.dao.UserDao;
import com.yangzc.studentboot.system.domain.UserDO;
import com.yangzc.studentboot.system.service.MenuService;
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 java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class UserRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
Long userId = ShiroUtils.getUserId();
MenuService menuService = ApplicationContextRegister.getBean(MenuService.class);
Set<String> perms = menuService.listPerms(userId);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(perms);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
Map<String, Object> map = new HashMap<>(16);
map.put("username", username);
String password = new String((char[]) token.getCredentials());
UserDao userMapper = ApplicationContextRegister.getBean(UserDao.class);
// 查询用户信息
UserDO user = userMapper.list(map).get(0);
// 账号不存在
if (user == null) {
throw new UnknownAccountException("账号或密码不正确");
}
// 密码错误
if (!password.equals(user.getPassword())) {
throw new IncorrectCredentialsException("账号或密码不正确");
}
// 账号锁定
if (user.getStatus() == 0) {
throw new LockedAccountException("账号已被锁定,请联系管理员");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
return info;
}
}
登录接口
package com.yangzc.studentboot.login;
import com.yangzc.studentboot.common.config.StudentBootConfig;
import com.yangzc.studentboot.common.controller.BaseController;
import com.yangzc.studentboot.common.domain.FileDO;
import com.yangzc.studentboot.common.domain.Tree;
import com.yangzc.studentboot.common.service.FileService;
import com.yangzc.studentboot.common.utils.*;
import com.yangzc.studentboot.system.domain.MenuDO;
import com.yangzc.studentboot.system.service.MenuService;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@Controller
public class LoginController extends BaseController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
StudentBootConfig studentBootConfig;
@Autowired
MenuService menuService;
@Autowired
FileService fileService;
@GetMapping({"/", ""})
String welcome(Model model) {
return "redirect:/blog";
}
@GetMapping({"/index"})
String index(Model model) {
List<Tree<MenuDO>> menus = menuService.listMenuTree(getUserId());
model.addAttribute("menus", menus);
model.addAttribute("name", getUser().getName());
FileDO fileDO = fileService.get(getUser().getPicId());
if (fileDO != null && fileDO.getUrl() != null) {
if (fileService.isExist(fileDO.getUrl())) {
model.addAttribute("picUrl", fileDO.getUrl());
} else {
model.addAttribute("picUrl", "/img/photo_s.jpg");
}
} else {
model.addAttribute("picUrl", "/img/photo_s.jpg");
}
model.addAttribute("username", getUser().getUsername());
return "index";
}
@GetMapping("/login")
String login(Model model) {
model.addAttribute("username", studentBootConfig.getUsername());
model.addAttribute("password", studentBootConfig.getPassword());
return "login";
}
@PostMapping("/login")
@ResponseBody
R ajaxLogin(String username, String password, String verify, HttpServletRequest request) {
try {
//从session中获取随机数
String random = (String) request.getSession().getAttribute(RandomValidateCodeUtil.RANDOMCODEKEY);
if (StringUtils.isBlank(verify)) {
return R.error("请输入验证码");
}
if (random.equals(verify)) {
} else {
return R.error("请输入正确的验证码");
}
} catch (Exception e) {
logger.error("验证码校验失败", e);
return R.error("验证码校验失败");
}
password = MD5Utils.encrypt(username, password);
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return R.ok();
} catch (AuthenticationException e) {
return R.error("用户或密码错误");
}
}
@GetMapping("/logout")
String logout() {
ShiroUtils.logout();
return "redirect:/login";
}
@GetMapping("/main")
String main() {
return "main";
}
/**
* 生成验证码
*/
@GetMapping(value = "/getVerify")
public void getVerify(HttpServletRequest request, HttpServletResponse response) {
try {
response.setContentType("image/jpeg");//设置相应类型,告诉浏览器输出的内容为图片
response.setHeader("Pragma", "No-cache");//设置响应头信息,告诉浏览器不要缓存此内容
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expire", 0);
RandomValidateCodeUtil randomValidateCode = new RandomValidateCodeUtil();
randomValidateCode.getRandcode(request, response);//输出验证码图片方法
} catch (Exception e) {
logger.error("获取验证码失败>>>> ", e);
}
}
}
gibhub项目地址
https://github.com/yangzc23/studentboot.git
参考资料
[01] Springboot整合Mybatis提示Mapper没有注入的问题
[02] Spring Boot使用Shiro实现登录授权认证
[03] SpringBoot could not autowire
[04] idea配置热部署
[05] IDEA中thymeleaf页面变量报错
[06] thymeleaf的内联th:inline(在javascript访问model中的数据)
[07] 警告:Establishing SSL connection without server’s identity verification is not recommended
[08] springboot2.0 常见问题找不到dao
[09] SpringBoot整合Mybatis扫描不到Mapper的问题
[10] 解决:SpringBoot2.x启动报failed to convert java.lang.String to org.springframework.util.unit.DataSize
[11] springboot集成themeleaf报Namespace ‘th’ is not bound
[12] springboot 日志的调用及日志文件路径的设置
微信扫一扫关注公众号
点击链接加入群聊
https://jq.qq.com/?_wv=1027&k=5eVEhfN
软件测试学习交流QQ群号:511619105
上一篇: Nginx实现与tomcate集群
下一篇: MySQL数字类型中的三种常用种类
推荐阅读
-
Python使用htpasswd实现基本认证授权的例子
-
TP5(thinkPHP5)框架使用ajax实现与后台数据交互的方法小结
-
Springboot shiro认证授权实现原理及实例
-
使用SpringBoot和SpringSecurity实现 JWT 认证与授权
-
shiro源码篇 - shiro认证与授权,你值得拥有
-
Python使用htpasswd实现基本认证授权的例子
-
Laravel5.1 框架模型一对一关系实现与使用方法实例分析
-
复习之shiro安全框架(一)——初步了解及使用shiro认证
-
使用微服务架构思想,设计部署OAuth2.0授权认证框架
-
springboot+sercuity+oauth2+Jwt+手机号+微信+密码 企业级认证与授权原理以及实现(完整版)