Shiro 简单入门
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
三个核心组件:Subject, SecurityManager 和 Realms.
Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
第一步创建Springboot项目
通过自己熟悉的工具,创建一个Springboot项目
pom.xml
<?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.1.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.zdltech.demo</groupId> <artifactId>shiro</artifactId> <version>0.0.1-SNAPSHOT</version> <name>shiro</name> <description>shiro demo</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <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.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--shiro 依赖添加--> <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
配置Shiro
创建一个ShiroConfig进行配置
import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig { private Logger logger = LoggerFactory.getLogger(ShiroConfig.class); @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){ logger.info("ShiroConfig initalized"); // 初始化ShiroFilterFactoryBean ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //必须设置SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); //拦截器 Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>(); //退出 filterChainDefinitionMap.put("/logout","logout"); // 过滤链定义,从上向下顺序执行,一般将/** 放到最下面 filterChainDefinitionMap.put("/admin/**","authc"); filterChainDefinitionMap.put("/**","anon"); // 如果不设置默认会自动寻找Web工程更目录下的“login.jsp”页面 shiroFilterFactoryBean.setLoginUrl("/login"); // 登录成功后要跳转的连接 shiroFilterFactoryBean.setSuccessUrl("/success"); // 未授权界面 shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager(){ System.out.println("securityManager is run"); SecurityManager securityManager = new DefaultWebSecurityManager(); ((DefaultWebSecurityManager) securityManager).setRealm(new MyRealm()); return securityManager; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ System.out.println("authorizationAttributeSourceAdvisor is run"); AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){ System.out.println("defaultAdvisorAutoProxyCreator is run"); DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } }
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.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.PostConstruct; public class MyRealm extends AuthorizingRealm { private Logger logger = LoggerFactory.getLogger(MyRealm.class); @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {//权限认证,即登录过后,每个身份不一定,对应的所能看的页面也不一样。 //认证 并且获取角色及权限 logger.info("#################执行Shiro权限认证#######################"); String userName = (String) principalCollection.getPrimaryPrincipal(); if (userName.equals("admin")){ SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermission("admin:*"); info.addStringPermission("index:*"); info.addRole("admin"); return info; }else if (userName != null&& userName.length()>0){ SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermission("index:*"); info.addRole("index"); return info; }else{ return null; } } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { logger.info("#################执行doGetAuthenticationInfo认证#######################"); UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; logger.info("user==>"+token.getUsername()); if ("admin".equals(token.getUsername())){ return new SimpleAuthenticationInfo(token.getUsername(),"123456",token.getUsername()); }else if (token.getUsername() != null&& token.getUsername().length()>0){ return new SimpleAuthenticationInfo(token.getUsername(),"111111",token.getUsername()); }else{ return null; } } @PostConstruct public void initCredentialsMatcher(){ setCredentialsMatcher(new CredentialsMatcher()); } }
import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.credential.SimpleCredentialsMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class CredentialsMatcher extends SimpleCredentialsMatcher { private Logger logger = LoggerFactory.getLogger(CredentialsMatcher.class); @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { System.out.println("doCredentialsMatch is run!!!"); UsernamePasswordToken authToken = (UsernamePasswordToken) token; if ("admin".equals(authToken.getUsername())){ return getCredentials(info).equals("123456"); }else if( authToken.getUsername()!=null && authToken.getUsername().length()>0){ return getCredentials(info).equals("111111"); }else { return false; } } }
通过这些我们配置完成Shiro
测试Shiro
最简单的基础的Shiro配置完成,下面我们来创建一个Controller测试下
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping("") public class IndexController { @GetMapping @ResponseBody public String defaultMethod(){ return "ok"; } @GetMapping("index") @ResponseBody public String index(){ return "index is run ! please continue"; } @GetMapping("login") public String login(){ return "login"; } @GetMapping("admin") @ResponseBody public String adminindex(){ System.out.println("admin IS run !"); Subject subject = SecurityUtils.getSubject(); subject.checkPermission("admin:*"); boolean has = subject.isPermitted("admin:*"); System.out.println("是否有权限:"+has); return "admin index is in"; } @GetMapping("loginrequest") @ResponseBody public String loginRequest(String userName,String password){ System.out.println("loginrequest IS run !"); if (userName.equals("admin")&&password.equals("123456")){ UsernamePasswordToken token =new UsernamePasswordToken(); token.setUsername(userName); token.setPassword(password.toCharArray()); System.out.println("登录成功!!!"); Subject subject = SecurityUtils.getSubject(); subject.login(token); }else if (userName!=null&&userName.length()>0){ UsernamePasswordToken token =new UsernamePasswordToken(); token.setUsername(userName); token.setPassword(password.toCharArray()); Subject subject = SecurityUtils.getSubject(); subject.login(token); }else{ System.out.println("登录失败!!!"); } return " index is in"; } }
对应的login.html为
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登录</title> </head> <body> <form action="/loginrequest"> 用户名: <input name="userName"/><br> 密码:<input name="password"/> <button type="submit">提交</button> </form> </body> </html>
SpringBoot 集成Shiro实现前后端分离
shiro从cookie中读取sessionId以此来维持会话,在前后端分离的项目中(也可在移动APP项目使用),我们选择在ajax的请求头中传递sessionId,因此需要重写shiro获取sessionId的方式。自定义MySessionManager类继承DefaultWebSessionManager类,重写getSessionId方法
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;public class MySessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "Authorization";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public MySessionManager() {
super();
}@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);
}
}
}使用自定义的SessionManager,在Shiro的config中
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
return securityManager;
}//自定义sessionManager @Bean public SessionManager sessionManager() { MySessionManager mySessionManager = new MySessionManager(); mySessionManager.setSessionDAO(redisSessionDAO());//此处是使用redis中的Session return mySessionManager; }
参考
在前后端分离的SpringBoot项目中集成Shiro权限框架