springboot + shiro 构建权限模块
权限模块基本流程
权限模块的基本流程:用户申请账号和权限 -->登陆认证 -->安全管控模块认证 -->调用具体权限模块(基于角色的权限控制) --> 登陆成功 -->访问资源 -->安全模块鉴权 -->通过后获取资源。整个流程如下图
常用的两个安全管控模块比较
JAAS,java验证和授权模块,jdk提供的一套标准的方法,对于有异构分布式的大型企业推荐这个框架,很多开源项目的安全框架就是JAAS,例如CAS 、tomcat、activemq等。
spring security,市场占有率最高的框架,支持范围广、功能更强大,但是学习比较复杂,成本比较高,跟spring框架绑定,无法在非spring项目中使用,对于安全需求复杂、支持范围广的项目可以使用。
shiro,简单易学,功能对于一般的web项目已经足够,相对独立,可用于非Spring的项目。
shiro基础知识
shiro扩展性很强,我们可以实现自己的realms、sessionDao、cachemanager以及自定义过滤器来实现个性化需求,其架构图如下:
shiro的核心概念有:
subject:用户,这个用户不一定使人,而是指任何与当前系统交互的对象
principal:身份标示,表示主体的抽象概念,可以用来表示任何实体,如username、ID、phone等
credentail:密码
token:principal+ credentail,用户在登陆时提交的
securityManager:安全管理器,shiro的核心模块,查看源码就可以从这个类开始看
authenticator:认证器,负责主体认证,其需要认证策略,决定什么用户可以认证通过
authorizer:鉴权器,决定用户是否有权限访问某个资源
releam:安全实体数据源,自带了多种实现,我们也可以扩展自己的
role:角色,权限和用户的桥梁
permissions:权限,shiro权限描述,printer:print:A,第一个代表域,第二个代表操作,第三个代表实例
shiro自带的过滤器实现:
springboot2.x整合shiro
项目依赖
<dependencies>
<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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>
application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver jpa:
database: mysql
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5Dialect thymeleaf:
cache: false
mode: HTML
自定义的安全实体数据源
package com.jlwj.shiro.config; import com.jlwj.shiro.model.SysPermission;
import com.jlwj.shiro.model.SysRole;
import com.jlwj.shiro.model.UserInfo;
import com.jlwj.shiro.service.UserInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
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; @Slf4j
public class MyShiroRealm extends AuthorizingRealm {
@Resource
private UserInfoService userInfoService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
log.info("principals:{}",principals);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
UserInfo userInfo = (UserInfo)principals.getPrimaryPrincipal();
for(SysRole role:userInfo.getRoleList()){
authorizationInfo.addRole(role.getRole());
for(SysPermission p:role.getPermissions()){
authorizationInfo.addStringPermission(p.getPermission());
}
}
return authorizationInfo;
} /*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
log.info("token:{}",token);
//获取用户的输入的账号.
String username = (String)token.getPrincipal();
System.out.println(token.getCredentials());
//通过username从数据库中查找 User对象,如果找到,没找到.
//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
UserInfo userInfo = userInfoService.findByUsername(username);
// log.info("userInfo:{}",userInfo);
if(userInfo == null){
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userInfo, //用户名
userInfo.getPassword(), //密码
ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt
getName() //realm name
);
return authenticationInfo;
} public void clearCached() {
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
super.clearCache(principals);
} }
自定义的过滤器,用于验证码校验
package com.jlwj.shiro.filter; import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession; /**
* @author hehang on 2019-08-14
* @description sdf
*/
@Slf4j
public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
log.info("自定义过滤器");
//在这里进行验证码的校验 //从session获取正确验证码
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpSession session =httpServletRequest.getSession();
//取出session的验证码(正确的验证码)
String validateCode = (String) session.getAttribute("validateCode");
log.info("session:{}",validateCode); //取出页面的验证码
//输入的验证和session中的验证进行对比
String randomcode = httpServletRequest.getParameter("randomcode");
String username = httpServletRequest.getParameter("username");
log.info("randomcode:{}",randomcode);
if(randomcode!=null && validateCode!=null && !randomcode.equals(validateCode)){
//如果校验失败,将验证码错误失败信息,通过shiroLoginFailure设置到request中
httpServletRequest.setAttribute("shiroLoginFailure", "randomCodeError");
//拒绝访问,不再校验账号和密码
return true;
}
//验证码通过,才会到用户认证否则直接到controller层
return super.onAccessDenied(request, response);
}
}
shiro核心配置
package com.jlwj.shiro.config; import com.jlwj.shiro.filter.CustomFormAuthenticationFilter;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties; /**
* @author hehang on 2019-08-13
* @description sd
*/
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("authc",formAuthenticationFilter());
shiroFilterFactoryBean.setFilters(filterMap);
//拦截器.
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
//静态资源放行
filterChainDefinitionMap.put("/static/**", "anon");
//配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
//所有请求必须登陆
filterChainDefinitionMap.put("/**", "authc");
// 设置登陆链接
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权链接
shiroFilterFactoryBean.setUnauthorizedUrl("/refuse");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
} /**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* )
* @return
*/ @Bean
public FormAuthenticationFilter formAuthenticationFilter(){
FormAuthenticationFilter formAuthenticationFilter = new CustomFormAuthenticationFilter();
formAuthenticationFilter.setPasswordParam("password");
formAuthenticationFilter.setUsernameParam("username");
formAuthenticationFilter.setRememberMeParam("rememberMe");
return formAuthenticationFilter;
}
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
} @Bean
public MyShiroRealm myShiroRealm(){
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
} @Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setCacheManager(cacheManager());
securityManager.setSessionManager(sessionManager());
securityManager.setRememberMeManager(rememberMeManager());
securityManager.setRealm(myShiroRealm());
return securityManager;
}
@Bean
public CacheManager cacheManager(){
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:shiro-ehcache.xml");
return cacheManager;
}
@Bean
public SessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(600000);
sessionManager.setDeleteInvalidSessions(true);
return sessionManager;
}
@Bean
public RememberMeManager rememberMeManager(){
CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
rememberMeManager.setCookie(rememberCookie());
return rememberMeManager;
}
@Bean
public SimpleCookie rememberCookie(){
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
simpleCookie.setMaxAge(2592000);
return simpleCookie;
} /**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
} //注册时加密方法
public static final String md5(String password, String salt){
//加密方式
String hashAlgorithmName = "MD5";
//盐:为了即使相同的密码不同的盐加密后的结果也不同
ByteSource byteSalt = ByteSource.Util.bytes(salt);
//密码
Object source = password;
//加密次数
int hashIterations = 2;
SimpleHash result = new SimpleHash(hashAlgorithmName, source, byteSalt, hashIterations);
return result.toString();
} }
MainController
package com.jlwj.shiro.controller; import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.Random; @Controller
public class MainController {
@RequestMapping({"/","/index"})
public String index(){
return"/index";
} @RequestMapping("/asd")
public String asd(){
return"/asd";
} @RequestMapping("/login")
public String login(HttpServletRequest request, Map<String, Object> map) throws Exception{
System.out.println("HomeController.login()");
// 登录失败从request中获取shiro处理的异常信息。
// shiroLoginFailure:就是shiro异常类的全类名.
String exception = (String) request.getAttribute("shiroLoginFailure");
System.out.println("exception=" + exception);
String msg = "";
if (exception != null) {
if (UnknownAccountException.class.getName().equals(exception)) {
System.out.println("UnknownAccountException -- > 账号不存在:");
msg = "UnknownAccountException -- > 账号不存在:";
} else if (IncorrectCredentialsException.class.getName().equals(exception)) {
System.out.println("IncorrectCredentialsException -- > 密码不正确:");
msg = "IncorrectCredentialsException -- > 密码不正确:";
} else if ("kaptchaValidateFailed".equals(exception)) {
System.out.println("kaptchaValidateFailed -- > 验证码错误");
msg = "kaptchaValidateFailed -- > 验证码错误";
} else {
msg = "else >> "+exception;
System.out.println("else -- >" + exception);
}
}
map.put("msg", msg);
String code = String.valueOf(new Random().nextInt(1000000));
request.getSession().setAttribute("validateCode",code);
map.put("code",code);
// 此方法不处理登录成功,由shiro进行处理
return "/login";
} @RequestMapping("/refuse")
public String unauthorizedRole(){
return "403";
} }
UserInfoController,代表资源
package com.jlwj.shiro.controller; import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; @Controller
@RequestMapping("/userInfo")
public class UserInfoController { /**
* 用户查询.
* @return
*/
@RequestMapping("/userList")
@RequiresPermissions("userInfo:view")//权限管理;
public String userInfo(){
return "userInfo";
} /**
* 用户添加;
* @return
*/
@RequestMapping("/userAdd")
@RequiresPermissions("userInfo:add")//权限管理;
public String userInfoAdd(){
return "userInfoAdd";
} /**
* 用户删除;
* @return
*/
@RequestMapping("/userDel")
@RequiresPermissions("userInfo:del")//权限管理;
public String userDel(){
return "userInfoDel";
}
}
encache配置文件
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!--diskStore:缓存数据持久化的目录 地址 -->
<diskStore path="/Users/hehang/tools/" />
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
持久化框架使用jpa,实体类要实现序列化接口,同时注意tostring方法的生成,排除调集合属性,这里就不贴出了
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
验证码:<h4 th:text="${code}"></h4>
<form action="" method="post">
<p>账号:<input type="text" name="username" value="admin"/></p>
<p>密码:<input type="text" name="password" value="123456"/></p>
<p>验证码:<input type="text" name="randomcode" value="1234"/></p>
<p><input type="checkbox" name="rememberMe" />自动登陆</p>
<p><input type="submit" value="登录"/></p>
</form>
错误信息:<h4 th:text="${msg}"></h4>
</body>
</html>
至此,实现了基本权限模块,包括了验证码校验、remember me功能和密码加密功能。
springboot + shiro 构建权限模块的更多相关文章
- SpringBoot&Shiro实现权限管理
SpringBoot&Shiro实现权限管理 引言 相信大家前来看这篇文章的时候,是有SpringBoot和Shiro基础的,所以本文只介绍整合的步骤,如果哪里写的不好,恳请大家能指出错误,谢 ...
- SpringBoot+Gradle构建多模块项目
1 概述 Gradle由于构建速度比Maven快,且比Maven灵活,因此很多后端的应用都使用了Gradle进行构建,但一个问题是,Gradle的多模块项目比较难构建,再加上Gradle的更新非常快, ...
- Gradle中使用SpringBoot插件构建多模块遇到的问题
通常下,多模块的项目如下: Root project 'demospring' +--- Project ':model' \--- Project ':rest' 那么我们需要在rest模块依赖mo ...
- Maven + springboot + mybatis 构建多模块工程
废话不说先上最终效果:创建一个空项目,再创建一个父项目用来管理各模块并维护各模块关系,简要说明如下: parent模块:主要用来管理以下各模块,和各模块涉及的jar包版本和boot项目入口级的的依赖管 ...
- Jenkins+Git+Maven构建并部署springboot(构建多模块中的单个模块)
主要思路:1.jenkins从git中拉取项目源码:jenkins使用maven构建并将生成的jar包通过shell脚本启动. 环境:环境:Centos7.Maven3.5.3.git(单机) 准备工 ...
- IDEA中使用springBoot+gradle构建多模块项目
https://blog.csdn.net/forMelo/article/details/78995875
- spring-boot-plus集成Shiro+JWT权限管理
SpringBoot+Shiro+JWT权限管理 Shiro Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理. 使用Shiro的易于理解的API,您可以 ...
- Spring-Boot构建多模块项目
Spring-Boot构建多模块项目 功能模块单独项目开发,可以将一个庞大的项目分解成多个小项目,便于细分开发 Maven多模块项目不能独立存在,必须有一个介质来包含. 1.创建一个Maven 项目, ...
- springboot+shiro+redis(单机redis版)整合教程-续(添加动态角色权限控制)
相关教程: 1. springboot+shiro整合教程 2. springboot+shiro+redis(单机redis版)整合教程 3. springboot+shiro+redis(集群re ...
随机推荐
- zabbix使用fping监控任意两个节点之间的网络质量、丢包率和响应时间
zabbix使用fping监控任意两个节点之间的网络质量.丢包率和响应时间 之前的博文 使用zabbix3..4的ICMP Ping模版实现对客户端网络状态的监控 https://www.cnblog ...
- 【转载】 tf.train.slice_input_producer()和tf.train.batch()
原文地址: https://www.jianshu.com/p/8ba9cfc738c2 ------------------------------------------------------- ...
- std::shared_mutex和std::mutex的性能对比(banchmark)
原文作者:@玄冬Wong 转载请注明原文出处:http://aigo.iteye.com/blog/2296462 key world: std::shared_mutex.std::mutex.pe ...
- 5-1 嵌套while循环应用
package com.imooc; public class forDemo { public static void main(String[] args) { ;//外重循环的循环变量: ;// ...
- 算法习题---5-2Ducci序列(UVa1594)
一:题目 对于一个n元组(a1, a2, …, an),可以对于每个数求出它和下一个数的差的绝对值,得到一个新的n元组(|a1-a2|, |a2-a3|, …, |an-a1|).重复这个过程,得到的 ...
- js 实现复制粘贴
js 实现复制粘贴 <!DOCTYPE html> <html><head> <meta http-equiv="Content-Type" ...
- Django中models定义的choices字典使用get_FooName_display()在页面中显示值
问题 在django的models.py 中,我们定义了一些choices的元组,类似一些字典值,一般都是下拉框或者单多选框,例如 0对应男 1对应女等等 看下例子: class Area(model ...
- log4net示例2-日志输入存入Access(转)
需求:基于log4net组建,创建Console程序将日志输出到Access数据库. 具体实施: (1)创建控制台程序. (2)控制台程序中,添加一个纯文本文件,文件命名为“log-Access.se ...
- 创建 LVM
1.将物理磁盘设备条带化为物理卷 # pvcreate /dev/sdb 查看物理卷: # pvs# pvdisplay 2.创建卷组,并添加 PV 到卷组 # vgcreate vg1 /dev/s ...
- Teaset-React Native UI 组件库
GitHub地址 https://github.com/rilyu/teaset/blob/master/docs/cn/README.md React Native UI 组件库, 超过 20 个纯 ...