Shiro实战+springboot集成
本文未排版点击链接阅读原文
注:本文只做流程分享,重点解读,源码已上传github,源码获取方式(左下角阅读原文)
Springboot+Shiro
版本介绍
Springboot: 2.0.5.RELEASE
Java: JDK1.8
Shiro: 1.4.1
MySQL:8.0
本文简介
该例子中使用Springboot集成Shiro的方式展示Shiro 授权,主要涉及技术点,tkmapper、Druid、maven、mysql、lombok
初始化mysql 数据库:init.sql见resources/db/init.sql
引入maven依赖:见pom.xml
修改generator.xml配置文件(涉及数据库连接地址信息以及对应数据表修改)
点击maven插件(mybatis-generator)生成entity,dao,mapper
生成的代码结构如下
到此权限代码的基础就已经搭建好了,使用的用户-角色-权限模型也是经典的权限设计模型
配置application.xml
server:
port: 9006
#数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource #Druid连接池
url: jdbc:mysql://localhost:3306/springboot-demo?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=true&nullCatalogMeansCurrent=true
username: root #数据库用户名
password: root #数据库密码
driver-class-name: com.mysql.cj.jdbc.Driver #mysql驱动
thymeleaf:
prefix: classpath:/templates/
suffix: .html
mybatis:
mapper-locations: classpath:/mapper/*.xml
configuration:
map-underscore-to-camel-case: true
#打印sql
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper:
identity: MYSQL # 配置主键自动增长(使用MYSQL原生方式)
logging:
level:
com.tz.springbootshiro: info
# 分页插件
pagehelper:
reasonable: true
page-size-zero: true
params: pageNum=start;pageSize=limit
support-methods-arguments: true
配置shiroconfig类,配置我们需要的组件
package com.tz.springbootshiro.config;
import com.oracle.tools.packager.Log;
import com.tz.springbootshiro.realm.MyRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.AuthenticationStrategy;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.apache.shiro.mgt.SecurityManager;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author tz
* @Classname ShiroConfig
* @Description shiro 配置
* @Date 2019-11-10 09:23
*/
@SpringBootConfiguration
public class ShiroConfig {
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* 所以我们需要修改下doGetAuthenticationInfo中的代码;
* )
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//散列的次数,比如散列两次,相当于 md5(md5(""));
hashedCredentialsMatcher.setHashIterations(2);
// 是否存储为16进制
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
/**
* Shiro默认提供了三种 AuthenticationStrategy 实现:
* AtLeastOneSuccessfulStrategy :其中一个通过则成功。
* FirstSuccessfulStrategy :其中一个通过则成功,但只返回第一个通过的Realm提供的验证信息。
* AllSuccessfulStrategy :凡是配置到应用中的Realm都必须全部通过。
* authenticationStrategy
*
* @return
*/
@Bean(name = "authenticationStrategy")
public AuthenticationStrategy authenticationStrategy() {
Log.info("----------->>>>>>>>>>>>ShiroConfiguration.authenticationStrategy()");
return new FirstSuccessfulStrategy();
}
/**
* 安全管理器,并把realm注入
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm());
return securityManager;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @RequiresPermissions("topic:list")
* @RequiresPermissions
* @RequiresRoles
* @RequiresUser
* @RequiresGuest
* @RequiresAuthentication
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/");
shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
//注意此处使用的是LinkedHashMap,是有顺序的,shiro会按从上到下的顺序匹配验证,匹配了就不再继续验证
//所以上面的url要苛刻,宽松的url要放在下面,尤其是"/**"要放到最下面,如果放前面的话其后的验证规则就没作用了。
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/static/**/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/captcha.jpg", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 自定义realm
*/
@Bean
public MyRealm myRealm(){
MyRealm userRealm = new MyRealm();
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return userRealm;
}
}
自定义Realm 授权验证使用代码
package com.tz.springbootshiro.realm;
import com.tz.springbootshiro.bean.SysUser;
import com.tz.springbootshiro.service.SysPermissionService;
import com.tz.springbootshiro.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
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.realm.Realm;
import org.apache.shiro.realm.text.TextConfigurationRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import java.util.List;
/**
* @author tz
* @Classname MyRealm
* @Description
* @Date 2019-11-09 11:44
*/
@Slf4j
public class MyRealm extends AuthorizingRealm {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysPermissionService sysPermissionService;
/**
* 授权
*
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SysUser sysUser = (SysUser) principals.getPrimaryPrincipal();
List<String> sysPermissions = sysPermissionService.selectPermissionByUserId(sysUser.getUserId());
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(sysPermissions);
log.info("------------>>>>>>>>>>>>>>>>>>>>>>>>>>>>doGetAuthorizationInfo");
return info;
}
/**
* 认证
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
SysUser sysUser = sysUserService.findByUserName(token.getUsername());
if (sysUser == null) {
return null;
}
log.info("------------>>>>>>>>>>>>>>>>>>>>>>>>>>>>doGetAuthenticationInfo");
return new SimpleAuthenticationInfo(sysUser, sysUser.getPassword().toCharArray(), ByteSource.Util.bytes(sysUser.getSalt()), getName());
}
/**
* 测试realm
*
* @return
*/
@Bean
public Realm realm() {
TextConfigurationRealm realm = new TextConfigurationRealm();
realm.setUserDefinitions("joe.coder=password,user\n" +
"jill.coder=password,admin");
realm.setRoleDefinitions("admin=read,write\n" +
"user=read");
realm.setCachingEnabled(true);
return realm;
}
}
- 到此基础配置就结束了,对应的数据库实体类相关关系类可在github查找,下面掩饰结果,首先分析拦截url
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/");
shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/static/**/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/captcha.jpg", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
上方代码,我们在shiroconfig配置中指定的,登录url为/login,此处为get请求,找到controller中我们的/login的地址进行跳转,此处需要注意的是我们的controller不能返回json体,即不能加入RequestBody注解,登录成功的url为/,此处没有该url所以代码不会跳转,我们实际项目中可以把此改为我们登录成功之后跳转的url,setUnauthorizedUrl配置我们未授权页面的url,然后下面配置我门不需要拦截的url,最后加上/**的拦截验证,url依次往下,注意顺序,这个拦截全部一定要放在最后,不然其他配置就不生效,下面放上我controller的代码
package com.tz.springbootshiro.controller;
import com.tz.springbootshiro.common.CodeMsg;
import com.tz.springbootshiro.common.ResultBean;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
/**
* @author tz
* @Classname LoginController
* @Description 登录
* @Date 2019-11-10 09:52
*/
@Controller
public class LoginController {
/**
* shiro 跳转登录页面使用
* @return
*/
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String defaultLogin() {
return "/login/login";
}
/**
* 登录页面表单提交使用
* @param username
* @param password
* @return
*/
@ResponseBody
@PostMapping(value = "/login")
public ResultBean login(@RequestParam("userName") String username, @RequestParam("password") String password) {
// 从SecurityUtils里边创建一个 subject
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备 token(令牌)
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 执行认证登陆
try {
subject.login(token);
} catch (UnknownAccountException uae) {
return new ResultBean(CodeMsg.ACCOUNT_NOT_FOUND);
} catch (IncorrectCredentialsException ice) {
return new ResultBean(CodeMsg.PASSWORD_ERROR);
} catch (LockedAccountException lae) {
return new ResultBean(CodeMsg.ACCOUNT_LOCK);
} catch (ExcessiveAttemptsException eae) {
return new ResultBean(CodeMsg.ERROR_NUM_MORE);
} catch (AuthenticationException ae) {
return new ResultBean(CodeMsg.USERNAME_PASSWORD_ERROR);
}
if (subject.isAuthenticated()) {
return new ResultBean(CodeMsg.LOGIN_SUCCESS);
} else {
token.clear();
return new ResultBean(CodeMsg.LOGIN_ERROR);
}
}
}
ok,教程到此结束,纯干货,源码----->阅读原文
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律