SpringBoot整合Shiro源码地址:shiro: 安全框架Shiro 它是不需要依赖任何框架,就能使用的安全框架 (gitee.com)
org.apache.shiroshiro-spring-boot-web-starter1.9.0
目录结构:
application.properties
#Shiro相关shiro.secretKey=saltshiro.algorithmName=md5shiro.iterations=3#配置不需要登录就能访问的url (第一个是登录页面接口,第二个是用户登录认证的接口,后续根据需求添加)shiro.excludePathPatterns=/path/login,/user/userLogin
application.yml
server:address: 192.168.3.26 #不写可以使用localhost访问port: 60871spring:application:name: shiro-logindatasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://192.168.126.130:3306/user_db" />
配置文件的映射实体类
package com.dj.shiro.config.properties;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import java.util.List;/** * User: ldj * Date: 2023/5/16 * Time: 17:53 * Description: No Description */@Data@ConfigurationProperties(prefix = "shiro")public class ShiroConfigProperties {/** * cookie相关 */private String cookieName = "rememberMe";private String cookieDomain = "domain";private String cookiePath = "/";private Integer cookieMaxAge = 15 * 24 * 60 * 60;private Boolean onlySupportHttp = true;/** * CookieRememberMeManager 密钥 (必须是16位,否则报错) */private String cipherKey = "1234567890abcdef";/** * 加密盐 */private String secretKey;/** * 算法名称 */private String algorithmName;/** * 迭代次数 */private Integer iterations;/** * 放行登录页面接口和登录接口等url */private List excludePathPatterns;}
自定义Realm
package com.dj.shiro.realm;import com.dj.shiro.model.dto.PrincipalDto;import com.dj.shiro.model.entity.UserEntity;import com.dj.shiro.service.UserService;import com.dj.shiro.utils.JacksonUtil;import lombok.Getter;import lombok.Setter;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.StringUtils;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.apache.shiro.util.ByteSource;import java.util.Collections;import java.util.List;import java.util.Objects;/** * User: ldj * Date: 2023/5/15 * Time: 0:06 * Description: 自定义Realm,组件的作用获取Dao数据和前端页面输入数据,第三方Bean统一在ShiroConfig声明 */@Slf4j@Setter@Getterpublic class MyRealm extends AuthorizingRealm {private String salt;private UserService userService;public MyRealm(UserService userService, String salt) {this.salt = salt;this.userService = userService;}//1.获取认证信息@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {//1.获取用户传入的登录信息UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;String principal = usernamePasswordToken.getPrincipal().toString();log.info("[doGetAuthenticationInfo] principal:{}", principal);//2.根据用户名查询用户信息UserEntity userEntity = userService.getUserInfoByName(principal);log.info("[doGetAuthenticationInfo] userEntity:{}", JacksonUtil.writeValueAsString(userEntity));//3.封装一个认证数据包(包括用户上传的明文密码+从数据库查出来的加密后的密码),然后交给SecurityManager进行认证AuthenticationInfo authenticationInfo = null;if (Objects.nonNull(userEntity)) {PrincipalDto principalDto = PrincipalDto.builder().id(userEntity.getId()).name(userEntity.getName()).build();authenticationInfo = new SimpleAuthenticationInfo(principalDto,//用户身份信息Dto(可根据需求自定义)userEntity.getPwd(), //暗码("z3"经过MD5加盐迭代3次加密)ByteSource.Util.bytes(salt),//加密所使用的盐(必须跟数据库暗文密码所使用的盐是一样的)principal);//给数据包名(可随意自定义)}return authenticationInfo;}//2.获取授权信息@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();//参数principals.primaryPrincipal来自上面的PrincipalDtoPrincipalDto primaryPrincipal = (PrincipalDto) principals.getPrimaryPrincipal();log.info("[doGetAuthorizationInfo] primaryPrincipal:{}", JacksonUtil.writeValueAsString(primaryPrincipal));if (Objects.nonNull(primaryPrincipal)){String name = primaryPrincipal.getName();if(StringUtils.isNotBlank(name)){//根据用户身份信息查询用户角色列表List roles = userService.getRolesByName(name);log.info("[doGetAuthorizationInfo] roles:{}", JacksonUtil.writeValueAsString(roles));authorizationInfo.addRoles(roles);authorizationInfo.setObjectPermissions(Collections.emptySet());authorizationInfo.setStringPermissions(Collections.emptySet());}}return authorizationInfo;}}
Shiro的配置类
package com.dj.shiro.config;import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;import com.dj.shiro.common.Constant;import com.dj.shiro.config.properties.ShiroConfigProperties;import com.dj.shiro.realm.MyRealm;import com.dj.shiro.service.UserService;import com.dj.shiro.utils.JacksonUtil;import lombok.Setter;import lombok.extern.slf4j.Slf4j;import org.apache.shiro.authc.credential.HashedCredentialsMatcher;import org.apache.shiro.authc.pam.*;import org.apache.shiro.cache.CacheManager;import org.apache.shiro.cache.ehcache.EhCacheManager;import org.apache.shiro.io.ResourceUtils;import org.apache.shiro.realm.AuthenticatingRealm;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.realm.Realm;import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;import org.apache.shiro.web.mgt.CookieRememberMeManager;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.apache.shiro.web.servlet.SimpleCookie;import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.io.IOException;import java.io.InputStream;import java.util.ArrayList;import java.util.List;/** * User: ldj * Date: 2023/5/15 * Time: 1:23 * Description: 第三方Bean统一使用Config配置注入容器 */@Slf4j@Setter@Configuration@EnableConfigurationProperties(ShiroConfigProperties.class)public class ShiroConfig {/* * 注意: * 1.@Bean 出现报错某Bean已经被Shiro自动配置类声明,需要修改Bean的名称,默认命名是方法名 * 2.过滤器链添加拦截范围是有顺序要求的 */@Autowiredprivate UserService userService;//设置Shiro过滤器链拦截范围@Beanpublic DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition(ShiroConfigProperties properties) {log.info("[ShiroConfig] 启用自定义Shiro-FilterChain过滤器链");DefaultShiroFilterChainDefinition shiroFilterChain = new DefaultShiroFilterChainDefinition();//设置不需要认证可以访问的路径 (最好做成可配置的)List excludePathPatterns = properties.getExcludePathPatterns();log.info("[ShiroConfig] 放行无需认证的路径: " + JacksonUtil.writeValueAsString(excludePathPatterns));if (excludePathPatterns.size() > 0) {for (String excludePathPattern : excludePathPatterns) {shiroFilterChain.addPathDefinition(excludePathPattern, Constant.ShiroConstant.ANON_FILTER);}}//设置登出拦截器,登出接口Shiro已经帮实现("/logout"固定的)shiroFilterChain.addPathDefinition("/logout", Constant.ShiroConstant.LOGOUT_FILTER);//设置需要进行登录认证的拦截范围 ("/**" 一定放在最后)shiroFilterChain.addPathDefinition("/**", Constant.ShiroConstant.AUTHC_FILTER);shiroFilterChain.addPathDefinition("/**", Constant.ShiroConstant.USER_FILTER);return shiroFilterChain;}//Shiro开启注解功能@Beanpublic DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();advisorAutoProxyCreator.setProxyTargetClass(true);return advisorAutoProxyCreator;}//设置SecurityManager(核心)@Bean(name = "defaultWebSecurityManager")public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("customRealm") AuthenticatingRealm myRealm, @Qualifier("shiroCacheManager") CacheManager shiroCacheManager, @Qualifier("customRememberMeManager") CookieRememberMeManager rememberMeManager, @Qualifier("modularRealmAuthenticator") ModularRealmAuthenticator modularRealmAuthenticator) {log.info("[ShiroConfig] 启用自定义Shiro-SecurityManager安全管理");List realmList = new ArrayList();realmList.add(myRealm);//realmList.add(myRealm1);//realmList.add(myRealm2);DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);//设置认证器(使用默认策略)defaultWebSecurityManager.setRealms(realmList); //设置自定义RealmsdefaultWebSecurityManager.setRememberMeManager(rememberMeManager);//设置自定义rememberMeManagerdefaultWebSecurityManager.setCacheManager(shiroCacheManager); //设置缓存管理器(Ehcache)return defaultWebSecurityManager;}//设置缓存管理器,使用Ehcache作为缓存容器//点击登录时会打印一次sql,再点击测试角色授权接口或测试权限授权接口,就不不会查数据库,而实从缓存容器Ehcache获取@Bean(name = "shiroCacheManager")public CacheManager shiroCacheManager(){InputStream inputStream = null;try {inputStream = ResourceUtils.getInputStreamForPath("classpath:ehcache-shiro.xml");} catch (IOException e) {e.printStackTrace();}//net.sf.ehcache.CacheManager managernet.sf.ehcache.CacheManager ehCacheManager = new net.sf.ehcache.CacheManager(inputStream);//org.apache.shiro.cache.ehcacheEhCacheManager shiroCacheManager = new EhCacheManager();//包装一下shiroCacheManager.setCacheManager(ehCacheManager);return shiroCacheManager;}//设置认证器策略@Bean(name = "modularRealmAuthenticator")public ModularRealmAuthenticator modularRealmAuthenticator(@Qualifier("atLeastOneSuccessfulStrategy") AbstractAuthenticationStrategy authenticationStrategy) {ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();modularRealmAuthenticator.setAuthenticationStrategy(authenticationStrategy);return modularRealmAuthenticator;}//认证器拥有3种可选策略@Bean(name = "atLeastOneSuccessfulStrategy")public AbstractAuthenticationStrategy atLeastOneSuccessfulStrategy() {//1.只要有一个realm认证通过就算成功(默认策略)return new AtLeastOneSuccessfulStrategy();}@Bean(name = "firstSuccessfulStrategy")public AbstractAuthenticationStrategy firstSuccessfulStrategy() {//2.第一个realm认证成功,就算认证成功return new FirstSuccessfulStrategy();}@Bean(name = "allSuccessfulStrategy")public AbstractAuthenticationStrategy allSuccessfulStrategy() {//3.所有realm认证成功才是成功return new AllSuccessfulStrategy();}//创建Shiro的Cookie管理对象@Bean(name = "customRememberMeManager")public CookieRememberMeManager customRememberMeManager(@Qualifier("simpleCookie") SimpleCookie simpleCookie, ShiroConfigProperties properties) {CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();cookieRememberMeManager.setCookie(simpleCookie);cookieRememberMeManager.setCipherKey(properties.getCipherKey().getBytes());return cookieRememberMeManager;}//设置Cookie属性@Bean(name = "simpleCookie")public SimpleCookie simpleCookie(ShiroConfigProperties properties) {SimpleCookie cookie = new SimpleCookie(properties.getCookieName());//设置跨域//cookie.setDomain(domain);cookie.setPath(properties.getCookiePath());cookie.setHttpOnly(properties.getOnlySupportHttp());cookie.setMaxAge(properties.getCookieMaxAge()); //默认15天return cookie;}//设置密码匹配器@Bean(name = "credentialsMatcher")public HashedCredentialsMatcher hashedCredentialsMatcher(ShiroConfigProperties properties) {HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName(properties.getAlgorithmName()); //md5加密算法hashedCredentialsMatcher.setHashIterations(properties.getIterations()); //迭代3次加密return hashedCredentialsMatcher;}//用于解析Shiro页面,屏蔽用户没有权限的操作页面按钮@Beanpublic ShiroDialect shiroDialect(){return new ShiroDialect();}//将自定义的Realm对象注入Spring容器,如果还有多个自定义realm,则继续添加到SpringBean容器,并往realmList添加即可@Bean(name = "customRealm")public AuthorizingRealm myRealm(@Qualifier("credentialsMatcher") HashedCredentialsMatcher credentialsMatcher,ShiroConfigProperties properties) {MyRealm myRealm = new MyRealm(userService, properties.getSecretKey());myRealm.setCredentialsMatcher(credentialsMatcher);return myRealm;}}
>>>>入门测试Demo (与上面无关)
package com.dj.shiro;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.authz.AuthorizationException;import org.apache.shiro.crypto.hash.Md5Hash;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.realm.text.IniRealm;import org.apache.shiro.subject.Subject;import org.junit.jupiter.api.Test;import org.springframework.boot.test.context.SpringBootTest;//@SpringBootTestclass ShiroDemoApplicationTests {/** * 认证流程 * ApplicationCode --> Subject.login() ---> SecurityManager(Realm).login() */@Test //认证测试public void testShiroAuthentication() {//1.初始化获取SecurityManager,读取了shiro.ini配置信息,模拟查询数据库所有用户信息DefaultSecurityManager securityManager = new DefaultSecurityManager();securityManager.setRealm(new IniRealm("classpath:shiro.ini"));SecurityUtils.setSecurityManager(securityManager);//2.获取Subject对象,底层封装了Shiro的SecurityManager(核心干活的)Subject subject = SecurityUtils.getSubject();//3.获取PC信息(账号&密码)-->封装成AuthenticationToken对象(LoginDto)AuthenticationToken authenticationToken = new UsernamePasswordToken("zhangsan", "z3");//4.subject调用login(token)方法,完成认证登录,原理:securityManager遍历所有用户数据与token信息比对,比对成功就是登录成功try {subject.login(authenticationToken);System.out.println("登录成功");//5.根据角色进行授权(资源访问控制)boolean hasRole = subject.hasRole("role1");System.out.println("是否拥有角色role1 " + "[" + hasRole + "]");//try {//subject.checkPermission("user:insert1");//} catch (AuthorizationException e) {//System.out.println("没有操作权限 UnauthorizedException!");//e.printStackTrace();//}boolean permitted = subject.isPermitted("user:insert");System.out.println("是否拥有权限user:insert " + "[" + permitted + "]");} catch (AuthenticationException e) {System.out.println("登录失败!");e.printStackTrace();}}/** * 授权流程 * ApplicationCode --->Subject.isPermitted()/hasRole() ---> SecurityManager--->Authorizer */@Test //授权测试public void testShiroAuthorization() {//1.初始化获取SecurityManager,读取了shiro.ini配置信息,模拟查询数据库所有用户信息DefaultSecurityManager securityManager = new DefaultSecurityManager();securityManager.setRealm(new IniRealm("classpath:shiro.ini"));SecurityUtils.setSecurityManager(securityManager);//2.获取Subject对象,底层封装了Shiro的SecurityManager(核心干活的)Subject subject = SecurityUtils.getSubject();try {//配置文件权限内容不匹配,就抛异常subject.checkPermission("user:insert1");} catch (AuthorizationException e) {System.out.println("没有操作权限 UnauthorizedException!");e.printStackTrace();}//没经过登录,subject就没有角色信息,没有角色信息就没有对应的权限信息,所以还是falseboolean permitted = subject.isPermitted("user:insert");System.out.println("是否拥有权限user:insert " + "[" + permitted + "]");}@Test //测试Shiro加密器public void testPasswordEncoder() {String salt = "sfddfgdr43545";String rawPassword = "z3";for (int i = 0; i < 3; i++) {Md5Hash encoderPassword = new Md5Hash(rawPassword, salt,3);System.out.println("使用MD5加密后的密码 " + encoderPassword);}}}
shiro.ini配置文件
[users]zhangsan=z3,role1,role2lisi=l4[roles]role1=user:insert,user:select
补充:Shiro常用注解
@RequiresAuthentication ------>subject.isAuthentication();
@RequiresUser ------->subject.isAuthenticationed() +subject.isRemembered()
@RequiesPermissions --->判断是否拥有某权限
@RequiresGuest ---->判断是否是游客请求
@RequiresRolse ---->判断subject是否拥有某角色