shiro简介
什么是shiro
Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。Shiro是Apache软件基金会的一个开源项目。Shiro可以轻松地保护任何应用程序,从命令行应用程序到最大的企业Web应用程序。
shiro三大主体
Shiro的三大主体分别是Subject、SecurityManager、Realm。
Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;所有Subject都绑定到SecurityManager上,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;
Subject中比较常用的方法为:
方法 | 作用 |
---|---|
login(AuthenticationToken token) | 登录认证,通过传入合法的认证令牌(AuthenticationToken)进行认证,如果认证成功,则返回true。 |
logout() | 登出,清空当前用户的凭证信息并退出登录。 |
isAuthenticated() | 判断当前用户是否已经通过认证,如果已经通过认证,则返回true。 |
isRemembered() | 判断当前用户是否已经记住登录状态,如果已经记住登录状态,则返回true。 |
getPrincipal() | 获取当前用户的身份标识,可以是用户名、邮箱、手机号等。 |
getPrincipals() | 获取当前用户的身份集合,可以包含多个身份标识。 |
getSession() | 获取当前用户的会话对象(Subject.Session)。 |
getSession(boolean create) | 获取当前用户的会话对象,如果当前用户已经有会话,则返回已有的会话对象;如果当前用户没有会话,则创建一个新的会话对象。 |
hasRole(String roleName) | 判断当前用户是否拥有指定角色,如果拥有指定角色,则返回true。 |
hasRoles(Collection roleNames) | 判断当前用户是否拥有指定角色集合中的所有角色,如果都拥有,则返回true。 |
isPermitted(String permission) | 判断当前用户是否拥有指定权限,如果拥有,则返回true。 |
isPermittedAll(Collection permissions) | 判断当前用户是否拥有指定权限集合中的所有权限,如果都拥有,则返回true。 |
SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro框架的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器。
SecurityManager提供了以下常用方法:
方法 | 作用 |
---|---|
authenticate(AuthenticationToken) | 用于进行身份认证,接收一个AuthenticationToken参数,返回认证后的Subject对象。如果认证失败,会抛出相应的异常。 |
logout(Subject) | 用于注销用户登录信息,接收一个Subject参数,将当前用户的登录信息清除。 |
createSubject(SubjectContext) | 用于创建一个Subject对象,接收一个SubjectContext参数,返回创建成功的Subject对象。 |
hasRole(PrincipalCollection, String) | 判断当前用户是否拥有某个角色,接收一个PrincipalCollection参数和一个角色名字符串参数,返回一个布尔值表示是否拥有该角色。 |
isPermitted(PrincipalCollection, String) | 判断当前用户是否拥有某个权限,接收一个PrincipalCollection参数和一个权限名字符串参数,返回一个布尔值表示是否拥有该权限。 |
getRealms() | 获取当前SecurityManager中配置的Realm对象列表,返回一个List对象。 |
setRealms(List): | 设置当前SecurityManager中的Realm对象列表,接收一个List对象作为参数。 |
setSessionManager(SessionManager) | 设置Session管理器,接收一个SessionManager对象作为参数。 |
Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
shiro的引入
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.10.0</version>
</dependency>
springboot整合shiro的基本配置
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);//设置安全管理器
shiroFilterFactoryBean.setLoginUrl("/login");//默认登录的页面
shiroFilterFactoryBean.setSuccessUrl("/index");//登录成功返回的页面
shiroFilterFactoryBean.setUnauthorizedUrl("/403");//没有权限访问的页面
//设置
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/authc/**", "authc");
filterChainDefinitionMap.put("/**", "user");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager getSecurityManager(Realm realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
@Bean
public ShiroRealm getRealm() {
return new ShiroRealm();
}
}
springboot在整合shiro中是固定的三个步骤,首先就是要先创建一个ShiroFilterFactoryBean来设置SecurityManager以及过滤器,再创建一个SecurityManager的bean来设置Realm,之后在把自己自定义的Realm设置成bean。其中有一点需要注意的就是在设置ShiroFilterFactoryBean中的filterChainDefinitionMap的过滤链的时候最好使用LinkedHashMap,要是使用HashMap的话可能会出现一些问题。
shiro中常用的Filter
shiro的常用的系统自定义的Filter根据不同的设置来调用不同的过滤器,如下所示:
过滤器 | 对应的过滤器类 | 作用 |
---|---|---|
authc | FormAuthenticationFilter | 需要认证登录才能访问 |
user | UserFilter | 用户拦截器,表示必须存在用户 |
anon | AnonymousFilter | 匿名拦截器,不需要登录即可访问的资源,匿名用户或游客,一般用于过滤静态资源 |
roles | RolesAuthorizationFilter | 需要指定角色才能访问 |
port | PortFilter | 需要指定端口才能访问 |
perms | PermissionsAuthorizationFilter | 需要指定权限才能访问 |
logout | LogoutFilter | 登出过滤器,配置指定url就可以实现退出功能 |
authcBasic | BasicHttpAuthenticationFilter | 指定url需要basic登录 |
rest | HttpMethodPermissionFilter | 将http请求方法转化成相应的动词来构造一个权限字符串 |
ssl | SslFilter | 需要https请求才能访问 |
noSessionCreation | NoSessionCreationFilter | 禁止创建会话 |
shiro在DefaultFilter这个枚举类之中所定义的各种各样的过滤器,其中最常用的也就是authc,anon,在登出的时候或许也会设置logout这个过滤器,而这些过滤器也就是在设置ShiroFilterFactoryBean中的setFilterChainDefinitionMap方法过滤链所用到的。当然也可以自定义实现shiro中的过滤器,通常来说,在自定义实现过滤器都是继承的FormAuthenticationFilter或者AuthenticatingFilter这个类进行。但是自定义过滤器一般是加入一些其他的东西才会用到,比如用jwt所生成的token来代替shiro自带的。
自定义Realm
Shiro不是直接处理身份验证和权限控制的,而是通过与应用程序集成的Realm实现。Realm是Shiro身份验证和授权查询的后端数据源,它们通常与本地数据存储库(例如数据库)或远程数据存储库(例如LDAP)一起工作。Shiro具有一组默认的Realm,但您可以选择编写自定义Realm以处理自己的身份验证和授权数据源。
自定义Realm可以满足具体的业务需求,例如:
-
集成不同类型的数据源:Shiro默认支持JDBC和LDAP等数据源,但是如果您的应用程序需要使用其他类型的数据源(例如NoSQL数据库或REST API),则需要编写自定义Realm。
-
更好的性能:默认Realm需要处理大量权限控制逻辑,如果您想要更好的性能表现,则自定义Realm可以优化访问控制逻辑。
-
更好的安全性:默认Realm中的身份验证和授权逻辑是通用的,可能存在一些风险,自定义Realm可以根据具体的业务情况定制相应的安全策略。
需要注意的是,自定义Realm需要自己编写相应的身份验证和授权逻辑,并与应用程序进行整合。
public class ShiroRealm implements AuthorizingRealm {
@Override
public String getName() {
return "ShiroRealm";
}
//判断当前Realm是否支持某种类型的Token
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
//认证
@Override
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
// 根据用户名和密码查询用户信息
User user = userService.getUserByUsernameAndPassword(username, password);
if (user == null) {
throw new UnknownAccountException("用户名或密码错误");
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
return authenticationInfo;
}
//授权
@Override
public AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
// 根据用户名查询用户角色和权限信息
Set<String> roles = userService.getRolesByUsername(username);
Set<String> permissions = userService.getPermissionsByUsername(username);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
}
AuthorizingRealm是Shiro所提供的一个基于权限控制的Realm实现,它也是Shiro推荐的默认实现方式。与其他的Realm不同的是,AuthorizingRealm不仅仅具有验证身份的功能,还能通过实现doGetAuthorizationInfo()方法来完成授权操作,即根据用户的角色、权限等信息来决定用户是否拥有访问某个资源的权限。因此,如果开发者需要在自定义Realm中实现授权功能,那么继承AuthorizingRealm是一个非常合适的选择。同时,Shiro也为开发者提供了其他的Realm实现,例如:JdbcRealm、IniRealm等等,可以根据具体业务需求来选择使用。
shiro使用redis做缓存
在pom.xml文件中添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在引入redis的依赖之后则需要配置这三项:
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.data.redis.database=0
如果redis有密码的话,则需要通过spring.data.redis.password来配置密码进行连接。之后则是要来自定义自己的Cache和CacheManager进行操作。
public class RedisCache<k,v> implements Cache<k,v> {
private String CacheName;
public RedisCache() {
}
public RedisCache(String cacheName) {
this.CacheName = cacheName;
}
@Override
public v get(k k) throws CacheException {
System.out.println("get = " + k);
return (v) getRedisTemplate().opsForHash().get(this.CacheName,k.toString());
}
@Override
public v put(k k, v v) throws CacheException {
System.out.println("k = " + k+",v="+v);
getRedisTemplate().opsForHash().put(this.CacheName,k.toString(),v);
return null;
}
@Override
public v remove(k k) throws CacheException {
getRedisTemplate().opsForHash().delete(this.CacheName,k);
return null;
}
@Override
public void clear() throws CacheException {
getRedisTemplate().opsForHash().delete(this.CacheName);
}
@Override
public int size() {
return getRedisTemplate().opsForHash().size(this.CacheName).intValue();
}
@Override
public Set<k> keys() {
return getRedisTemplate().opsForHash().keys(this.CacheName);
}
@Override
public Collection<v> values() {
return getRedisTemplate().opsForHash().values(this.CacheName);
}
public RedisTemplate getRedisTemplate(){
RedisTemplate redisTemplate =(RedisTemplate) ApplicationContextUtils.getApplicationContext().getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
public class RedisCacheManager implements CacheManager {
@Override
public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
System.out.println(cacheName);
return new RedisCache<K,V>(cacheName);
}
}
在通过在配置类里面注册的Realm的bean,来将自定义Cache和CacheManager设置到所需要的Realm中。
@Bean
public ShiroRealm shiroRealm(){
ShiroRealm shiroRealm = new ShiroRealm();
shiroRealm.setCacheManager(new RedisCacheManager());//设置缓存管理器
shiroRealm.setAuthenticationCacheName("shiroRealmCache");
shiroRealm.setAuthorizationCacheName("shiroRealmCache");
shiroRealm.setAuthenticationCachingEnabled(true);
shiroRealm.setCachingEnabled(true);
return shiroRealm;
}