1. 简介
概念
说明
Subject
主体,简化点说就是用户实体
Principal
Subject的唯一标识,如id、用户名、手机号、邮箱等
Credential
凭证信息,主体证明自己的东西,如密码、证书等
Authenticator
认证器,对Subject身份进行认证,例如验证用户的用户名和密码是否匹配
Authorizer
授权器,通过认证器认证之后,要访问资源,还得获得资源授权
sessionManager
会话管理,不依赖web容器的session,可以将分布式应用的会话集中在一点管理,实现单点登录,自定义可继承DefaultWebSessionManager
SecurityManager
安全管理器,继承了Authenticator,Authorizer, SessionManager,对全部的subject进行安全管理
Realm
领域,实际认证和授权的地方,因为shiro不知道你的用户和密码相关数据,所以一般需要自定义Realm完成认证和授权
SessionDAO
对session会话操作的一套接口,自定义可继承EnterpriseCacheSessionDAO
CacheManager
缓存管理,将用户权限数据存储在缓存,避免每次访问数据库
Cryptography
密码管理,加密/解密的组件,如:常用的散列、加解密等功能
2. AuthorizingRealm
对于应用来说,Shiro的侵入虽然比较高,但是使用还是相对比较简单,如果不想太麻烦,只需要继承AuthorizingRealm,然后实现:
- doGetAutherizationInfo(PrincipalCollection principals)用于授权
- doGetAuthenticationInfo(AuthenticationToken token)用于认证
然后配置一下ShiroFilterFactoryBean,告诉Shiro资源的权限就可以。
3. 默认Filter
Shiro通过一系列filter来控制访问权限,并预先定义了很多过滤器(org.apache.shiro.web.filter.mgt.DefaultFilter),常用的过滤器配置如下:
字符串
说明
anon
所有用户可访问
authc
认证用户可访问
user
特定用户能访问
port
特定端口能访问,/user/**=port[8088]
http
特定http能访问,/user/**=perms[user:post]
roles
指定角色用户可访问,/admins/=roles[admin],admins/**=roles["admin,sys"]
perms
指定权限用户可访问,/admins/=perms[user:add:],/admins/**=perms["user:add:,user:modify:*"]
没有登录之前都跳转登录页面,登录之后检查权限,没有权限跳转未授权页面
4. 自定义Filter
自定义Filter可以继承AuthorizationFilter,当然也可以继承AccessControlFilter,或者:
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class OnlineSessionFilter extends AccessControlFilter {
/**
* 是否允许访问
* @mappedValue [urls]配置中拦截器参数部分
* @return 如果:true允许访问,否则不允许访问
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
// 检查逻辑
return true;
}
/**
* 当访问拒绝时是否继续处理
* @return 如果:true表示需要继续处理,否则直接返回
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
Subject subject = getSubject(request, response);
if (subject != null) {
subject.logout();
}
saveRequestAndRedirectToLogin(request, response);
return false;
}
}
然后在就可以这样使用:
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
Map<String, String> filterChainDefinitionMap = new HashMap<>();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/shiro/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/shiro/index");
// 访问未授权页面之后跳转链接
shiroFilterFactoryBean.setUnauthorizedUrl("/shiro/unauthc");
Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
filters.put("onlineSession", new OnlineSessionFilter());//自定义过滤器
shiroFilterFactoryBean.setFilters(filters);
// 所有请求需要认证
filterChainDefinitionMap.put("/**", "onlineSession");
filterChainDefinitionMap.put("/*", "anon");
filterChainDefinitionMap.put("/shiro/index", "authc");
filterChainDefinitionMap.put("/shiro/admin", "roles[admin]");
filterChainDefinitionMap.put("/shiro/insertUpdate", "perms[create,update]");
filterChainDefinitionMap.put("/shiro/delete", "perms[delete]");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
5. SimpleCredentialsMatcher
因为密码是直接取得数据库的密码,所以想要保证用户传入的密码能和我的密码能够匹配,需要创建自定义的密码校验规则。
可以继承SimpleCredentialsMatcher来实现自己的凭证验证,不过一般都使用HashedCredentialsMatcher,例如Sha256CredentialsMatcher。
6. 注解
注解
说明
@RequiresAuthentication
验证用户是否登录
@RequiresUser
验证用户是否登录,和@RequiresAuthentication不同记住我的用户也算
@RequiresGuest
验证是否是一个游客,没有登录
@RequiresRoles
必须是指定角色@RequiresRoles("admin"),@RequiresRoles(value={"admin", "sys"}, logical=Logical.OR)
@RequiresPermissions
必须有指定权限@RequiresPermissions({"delete", "write"})
7. 实例
如果要简单使用shiro,还是比较容易,下面给一个实例。
7.1 maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.7.0</version>
</dependency>
省略了SpringBoot相关的,选择自己喜欢的版本添加,shiro-spring1.7.0的shiro-ehcache现在暂时要自己单独添加一下,我看了它的配置是有,但是在自己项目中引用不了,所以手动添加一下。
7.2 属性文件配置
logging.config=classpath:logback.xml
spring.datasource.driverClassName = com.mysql.cj.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/data?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.username = tim
spring.datasource.password = 123456
spring.jpa.database=MySQL
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
7.3 shiro缓存配置
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="mycache-manager" updateCheck="false">
<!-- 磁盘缓存位置 -->
<diskStore path="java.io.tmpdir"/>
<!-- 默认缓存 -->
<defaultCache
maxEntriesLocalHeap="1000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600"
overflowToDisk="false">
</defaultCache>
<cache name="shiro"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="60"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="true">
</cache>
</ehcache>
7.4 实体类
用户类:
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import java.util.List;
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Integer id;
@Column(unique = true,length = 20)
private String username;
@Column(length = 64)
private String password;
private String salt;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "user_role", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns = {@JoinColumn(name = "rid") })
private List<Role> roles;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public String getCredentialsSalt() {
return username + salt + salt;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + "]";
}
}
角色类:
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import java.util.List;
@Entity
@Table(name = "role")
public class Role {
@Id
@GeneratedValue
private Integer id;
private String role;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "role_permission", joinColumns = { @JoinColumn(name = "rid") }, inverseJoinColumns = {@JoinColumn(name = "pid") })
private List<Permission> permissions;
@ManyToMany
@JoinTable(name = "user_role", joinColumns = { @JoinColumn(name = "rid") }, inverseJoinColumns = {@JoinColumn(name = "uid") })
private List<User> users;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public List<Permission> getPermissions() {
return permissions;
}
public void setPermissions(List<Permission> permissions) {
this.permissions = permissions;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
}
权限类:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import java.util.List;
@Entity
@Table(name = "permission")
public class Permission {
@Id
@GeneratedValue
private Integer id;
private String name;
@ManyToMany
@JoinTable(name = "role_permission", joinColumns = { @JoinColumn(name = "pid") }, inverseJoinColumns = {@JoinColumn(name = "rid") })
private List<Role> roles;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
}
7.5 Repository
import org.springframework.data.repository.CrudRepository;
import vip.mycollege.mysql.jpa.entity.User;
public interface UserRepository extends CrudRepository<User,String> {
User findUserByUsername(String username);
}
7.6 Realm
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 vip.mycollege.mysql.jpa.entity.Permission;
import vip.mycollege.mysql.jpa.entity.Role;
import vip.mycollege.mysql.jpa.entity.User;
import vip.mycollege.mysql.jpa.repository.UserRepository;
import javax.annotation.Resource;
public class UserPassRealm extends AuthorizingRealm {
@Resource
private UserRepository userRepository;
/**
* 登录
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
User user = userRepository.findUserByUsername(username);//从数据库查找username的用户
if (user == null) {
return null;
}
ByteSource salt = ByteSource.Util.bytes(user.getCredentialsSalt());
String realmName = this.getClass().getName();
// 有盐值的认证
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, user.getPassword(), salt, realmName);
// SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user,user.getPassword(),realmName);
return authenticationInfo;
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
String username = (String) principals.getPrimaryPrincipal();
User user = userRepository.findUserByUsername(username);//从数据库获取权限信息
if(user == null){
return null;
}
for (Role role : user.getRoles()) {
authorizationInfo.addRole(role.getRole());
for (Permission permission : role.getPermissions()) {
authorizationInfo.addStringPermission(permission.getName());
}
}
return authorizationInfo;
}
}
7.7 配置类
import com.google.common.io.Resources;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.Authorizer;
import org.apache.shiro.authz.ModularRealmAuthorizer;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.config.ConfigurationException;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
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.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import vip.mycollege.shiro.CredentialMatcher;
import vip.mycollege.shiro.OnlineSessionFilter;
import vip.mycollege.shiro.UserPassRealm;
import javax.servlet.Filter;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public Authorizer authorizer() {
return new ModularRealmAuthorizer();
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
Map<String, String> filterChainDefinitionMap = new HashMap<>();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/shiro/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/shiro/index");
// 访问未授权页面之后跳转链接
shiroFilterFactoryBean.setUnauthorizedUrl("/shiro/unauthc");
Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
filters.put("onlineSession", new OnlineSessionFilter());
shiroFilterFactoryBean.setFilters(filters);//设置自定义过滤器
// 所有请求需要认证
filterChainDefinitionMap.put("/**", "onlineSession");
filterChainDefinitionMap.put("/*", "anon");
filterChainDefinitionMap.put("/shiro/index", "authc");
filterChainDefinitionMap.put("/shiro/admin", "roles[admin]");
filterChainDefinitionMap.put("/shiro/insertUpdate", "perms[printer:insert,update]");
filterChainDefinitionMap.put("/shiro/delete", "perms[printer:delete]");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName(Sha256Hash.ALGORITHM_NAME); // 散列算法
hashedCredentialsMatcher.setHashIterations(3); // 散列次数
return hashedCredentialsMatcher;
}
/**
* 缓存管理器 使用Ehcache实现
*/
@Bean
public EhCacheManager getEhCacheManager() {
// 先查找是否已经创建了缓存管理器
net.sf.ehcache.CacheManager cacheManager = net.sf.ehcache.CacheManager.getCacheManager("shiro-ehcache");
if(cacheManager == null){//如果没有就在classpath下查找shiro-ehcache.xml配置文件创建缓存管理器
URL url = Resources.getResource("shiro-ehcache.xml");
if(url == null){
url = Resources.getResource("ehcache/shiro-ehcache.xml");
}
if(url == null){
throw new ConfigurationException("没有找到shiro-ehcache.xml配置文件");
}
cacheManager = net.sf.ehcache.CacheManager.create(url);
}
EhCacheManager em = new EhCacheManager();
em.setCacheManager(cacheManager);
return em;
}
@Bean("userPassRealm")
public UserPassRealm shiroRealm(EhCacheManager cacheManager) {//配置自定义的权限登录器
UserPassRealm shiroRealm = new UserPassRealm();
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
shiroRealm.setCacheManager(cacheManager);
shiroRealm.setAuthorizationCacheName("shiro");
// shiroRealm.setCredentialsMatcher(credentialMatcher());
return shiroRealm;
}
@Bean("securityManager")
public SecurityManager securityManager(@Qualifier("userPassRealm") Realm realm) {//配置核心安全事务管理器
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
/**
* cookie 属性设置
*/
public SimpleCookie rememberMeCookie() {
SimpleCookie cookie = new SimpleCookie("rememberMe");
cookie.setDomain("www.mycollege.vip");
cookie.setPath("/");
cookie.setHttpOnly(true);
cookie.setMaxAge(7 * 24 * 60 * 60);
return cookie;
}
/**
* 记住我
*/
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
cookieRememberMeManager.setCipherKey(Base64.decode("xxxxxx"));
return cookieRememberMeManager;
}
@Bean("credentialMatcher")
public CredentialMatcher credentialMatcher() { //配置自定义的密码比较器
return new CredentialMatcher();
}
/**
* 开启注解支持,方便使用:
*
* @RequiresPermissions
* @RequiresAuthentication
* @RequiresUser
* @RequiresGuest
* @RequiresRoles
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
}
7.8 Controller
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresGuest;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.annotation.RequiresUser;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import vip.mycollege.mysql.jpa.entity.User;
import vip.mycollege.mysql.jpa.repository.UserRepository;
import javax.annotation.Resource;
@RestController
@RequestMapping("/shiro")
public class ShiroController {
private static final RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
@Resource
private UserRepository userRepository;
@GetMapping("/index")
public String index() {
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getSession().getAttribute("user");
return user.toString();
}
@GetMapping("/admin")
public String admin() {
return "管理员页面";
}
@GetMapping("/delete")
public Object delete() {
return "需要删除相关权限页面";
}
@GetMapping("/insert-update")
public Object insertUpdate() {
return "需要插入更新相关权限页面";
}
@GetMapping("/login")
public String login() {
return "登录页面";
}
@GetMapping("/unauthc")
public String unauthc() {
return "该页面您还未获授权,暂时不能访问";
}
@GetMapping("/do-login")
public Object doLogin(@RequestParam String username, @RequestParam String password) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 记住我功能
// UsernamePasswordToken token = new UsernamePasswordToken(username, password,true);
//设置session
//SecurityUtils.getSubject().getSession().setAttribute("deployEnv", deployEnv);
//如果用户已登录,先踢出
//ShiroSecurityHelper.kickOutUser(username));
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
} catch (IncorrectCredentialsException ice) {
return "password error!";
} catch (UnknownAccountException uae) {
return "username error!";
}
User user = userRepository.findUserByUsername(username);
subject.getSession().setAttribute("user", user);
return "登录成功";
}
@GetMapping("/register")
public Object register(@RequestParam("username") String username, @RequestParam("password") String password) {
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.setSalt(randomNumberGenerator.nextBytes().toHex());
ByteSource byteSourceSalt = ByteSource.Util.bytes(user.getCredentialsSalt());
SimpleHash simpleHash = new SimpleHash(Sha256Hash.ALGORITHM_NAME, password, byteSourceSalt, 3);
String newPassword = simpleHash.toHex();
user.setPassword(newPassword);
userRepository.save(user);
return "注册成功";
}
@RequiresPermissions({"printer", "camera"})//默认and
@GetMapping("/requiresPermissions")
public String requiresPermissions(){
return "ok";
}
@RequiresRoles(value={"admin","sys"},logical= Logical.OR)
@GetMapping("/requiresRoles")
public String requiresRoles(){
return "ok";
}
@RequiresGuest
@GetMapping("/requiresGuest")
public String requiresGuest(){
return "ok";
}
@RequiresUser
@GetMapping("/requiresUser")
public String requiresUser(){
return "ok";
}
@RequiresAuthentication
@GetMapping("/requiresAuthentication")
public String requiresAuthentication(){
return "ok";
}
}
7.9 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StartApplication {
public static void main(String[] args) {
SpringApplication.run(StartApplication.class, args);
}
}
启动之后,会自动创建表,当然数据库配置要换成你自己的,没有的过滤器可以删除掉。
然后,在数据库中添加一点角色和权限数据就可以通过Controller进行测试了。
8. 测试
# 注册2个用户
http://localhost:8080/shiro/register?username=tim&password=123456
http://localhost:8080/shiro/register?username=bob&password=123456
# 游客访问
http://localhost:8080/shiro/requiresGuest
# 没有权限直接抛出异常
http://localhost:8080/shiro/requiresPermissions
# 检查角色,登录之后有权限执行
http://localhost:8080/shiro/requiresRoles
# 登录或者记住我
http://localhost:8080/shiro/requiresUser
# 必须是登录用户,用于一下敏感页面
http://localhost:8080/shiro/requiresAuthentication
# 登录
http://localhost:8080/shiro/do-login?username=tim&password=123456
更多的校验可以使用前面的实例自己测试,需要添加user、permssion、role相关数据。
当然,你也可以通过下面的测试类来进行测试:
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringBootTest
public class ShiroControllerTest {
@Autowired
protected WebApplicationContext wac;
protected MockMvc mockMvc;
@Before
public void setUp(){
this.mockMvc = webAppContextSetup(this.wac).build();
}
@Test
public void index() throws Exception {
mockMvc.perform(get("/shiro/index"))
.andExpect(status().isOk())
.andDo(new ResultHandler() {
@Override
public void handle(MvcResult result) throws Exception {
System.out.println(result.getResponse().getContentAsString());
}
})
.andReturn();
}
@Test
public void admin() {
}
@Test
public void delete() {
}
@Test
public void insertUpdate() {
}
@Test
public void login() throws Exception {
mockMvc.perform(get("/shiro/login")
.param("username", "tim")
.param("password", "123456")
)
.andExpect(status().isOk())
.andDo(new ResultHandler() {
@Override
public void handle(MvcResult result) throws Exception {
System.out.println(result.getResponse().getContentAsString());
}
})
.andReturn();
}
@Test
public void unauthc() {
}
@Test
public void doLogin() {
}
@Test
public void register() throws Exception {
mockMvc.perform(get("/shiro/register")
.param("username", "allen")
.param("password", "123456")
)
.andExpect(status().isOk())
.andDo(new ResultHandler() {
@Override
public void handle(MvcResult result) throws Exception {
System.out.println(result.getResponse().getContentAsString());
}
})
.andReturn();
}
@Test
public void requiresPermissions() {
}
@Test
public void requiresRoles() {
}
@Test
public void requiresGuest() {
}
@Test
public void requiresUser() {
}
@Test
public void requiresAuthentication() {
}
}
9. 权限
shiro的角色很好理解,但是权限比较难理解,所以放在最后来简单介绍一下。
首先:Subject有一个检查权限的方法:
boolean isPermitted(String permission);
具体实现是在DelegatingSubject:
public boolean isPermitted(String permission) {
return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
}
继续跟进AuthorizingSecurityManager的实现:
public boolean isPermitted(PrincipalCollection principals, String permissionString) {
return this.authorizer.isPermitted(principals, permissionString);
}
我们可以看到是通过Authorizer授权器来实现,以AuthorizingRealm为例的实现:
public boolean isPermitted(PrincipalCollection principals, String permission) {
Permission p = getPermissionResolver().resolvePermission(permission);
return isPermitted(principals, p);
}
可以看到是先获取到一个PermissionResolver,然后把字符串的permssion解析为Permission接口,然后通过Permission接口去校验。
AuthorizingRealm默认实现为解析出WildCardPermission,所以如果我们不想自己实现权限校验逻辑,就可以使用WildCardPermission。
问题的关键就是:WildCardPermission怎样校验权限?
10. WildCardPermission权限
DomainPermission有一个之类DomainPermission。
权限使用字符串定义,分三级,冒号分开:
资源(resource,domain):操作(action):实例(instance,操作在那其上面执行)
permission
说明
printer
所有实例都有资源printer的所有权限,等价于printer::
printer:query
资源上面指定query权限,等价于printer:query.*
printer:print,query
资源上面指定多个权限
printer:*
资源上面所用权限
*:view
所有资源都有view权限
printer:query:inst32
指定实例才有query权限
printer:print:*
所有实例都有print权限
printer::
所有实例都有资源printer的所有权限
printer:*:inst32
inst32实例有资源printer的所有权限
printer:query,print:inst32
inst32实例有资源printer的query和print权限
省略只能省略最右边的,资源必须指定。
WildCardPermission权限字符串都是你自己定义设计的,WildCardPermission只会去对比检查权限是否匹配,而并不关心权限的字符串是什么。
11. 文档资料
shiro官网 10分钟小实例 shiro guide shiro文档 shiro web shiro授权 shiro权限