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 {#toc_2}
> 创建一个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权限框架
💬 评论