Shiro功能介绍
下面介绍一下各功能点的意思:
Authentication: 身份认证/登录,验证用户是不是拥有相应的身份;
Authorization: 授权,即权限验证,验证某个已认证的用户是否拥有某个权限;就是说判断这个用户是否能做事情,例如:细粒度的验证某个用户对某个资源是否具有某个权限。
Session Management: 回话管理,即用户登录后就是一次会话,在没有退出之前,他的所有信息都在会话中;会话可以是javase环境,也可以是web环境。
Cryptography: 加密,保护数据的安全性;例如:密码加密存储到数据库,而不是明文存储。
Web Support: web支持,可以非常容易的集成到web环境。
Caching: 缓存,比如用户登录后,用户信息、拥有的角色\权限不必每次都去查,都可以在缓存取,提供效率。
Concurrency: shiro支持多线程的并发验证,即,如在一个线程中开启另一个线程,可以把权限自动传播过去。
Testing: 提供测试支持;
Run As: 允许一个用户假装为另一个用户(如果他们允许)进行访问。
Remember Me: 记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
Shiro架构
①Shiro是Java的一个安全框架
可以完成的功能:认证登录(Authentication)、授权(Authorization)、加密(cryptography)、会话管理(session management)、集成web(web support)、缓存(caching)、记住密码(remember me)。
②如何使用Shiro完成工作
subject shiro对外API核心就是Subject 。Subject代表当前用,这个登录对象不一定是人,可以是网络爬虫,机器人等。与Subject所有交互都会委托给SecurityManager,Subject是门面,SecurityManager才是真正的执行者。
SecurityManager 安全管理器:
所有与安全有关的操作都会放在SecurityManager中交互;
管理所有的Subject;
它是Shiro的核心,负责与shiro的其他组件进行交互,相当于SpringMVC中的DispatcherServlet。
Realm Shiro从Realm中获取安全数据(角色、用户、权限),它要验证用户身份,需要从Realm中获取相应的用户进行比较确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作,可以把Realm看成一个DataSource。
在Realm中,数据先从缓存中获取,如果在缓存中取不到再到数据库中获取,然后将数据放入缓存中。
一. Subject对象:
Subject subject = SecurityUtils.getSubject()
;
通过获取subjecet对象后,Subject可以做很多事:
-
subject.getSession()
;获取session对象,在获取到session后,就可以往session中存放session值了。 -
subject.isAuthenticated()
;判断当前对象是否被认证,即是否登录。 -
subject.login(token)
;登陆操作 -
subject.hasRole('role')
;对当前用户判断是否有一些角色信息. -
subject.isPermitted('user:delete:zhangsan')
; 判断当前主体是否有权限.判断用户是否有某种行为.当前代表user能够对zhangsan进行delete操作. -
subject.logout()
; 执行登出操作.
在subject调用login()方法的时候 , 会出现一些异常,这时候需要我们进行手动捕获这些异常,这些异常可能有 用户名不存在 UnknownAccountException
密码不正确IncorrectCredentialsException
账户被锁定LockedAccountException
以及其他的认证异常(AuthenticationException
).
二. token对象():
Shiro给我们提供了一个UsernamePasswordToken
对象,其中封装了有username
password
rememberMe
host
属性,其中我们可以对token对象进行继承并自定义,如添加是否是手机登录,添加email,别名等.subject判断当前登录主体是否被认证,如果发现没有登录,则需要将当前subject对象的信息放入到token中,subject对token进行登录验证.
Shiro认证
身份认证流程
-
1、首先调用 Subject.login(token) 进行登录,其会自动委托给 SecurityManager
-
2、SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;
-
3、Authenticator 才是真正的身份验证者,Shiro API 中核心的身份 认证入口点,此处可以自定义插入自己的实现;
-
4、Authenticator 可能会委托给相应的 AuthenticationStrategy 进 行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
-
5、Authenticator 会把相应的 token 传入 Realm,从 Realm 获取 身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处 可以配置多个Realm,将按照相应的顺序及策略进行访问。
Shiro权限注解
- @RequiresAuthentication:表示当前Subject已经通过login 进行了身份验证;即 Subject. isAuthenticated() 返回 true • @RequiresUser:表示当前 Subject 已经身份验证或者通过记 住我登录的。
- @RequiresGuest:表示当前Subject没有身份验证或通过记住 我登录过,即是游客身份。
- @RequiresRoles(value={“admin”, “user”}, logical= Logical.AND):表示当前 Subject 需要角色 admin 和user
- @RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR):表示当前 Subject 需要权限 user:a 或 user:b。
Shiro拦截器规则
ShiroConfig
注:本文config类一定要交给spring容器管理
①ShiroFilterFactoryBean
-
Shiro提供了与Web集成的支持,其通过一个
ShiroFilter
入口来拦截需要安全控制的URL,然后进行相应的控制 -
实例:
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
// Shiro的核心安全接口,这个属性是必须的(securityManager之前直接交给spring容器管理,所以直接从中获取)
shiroFilterFactoryBean.setSecurityManager(securityManager);
//身份认证失败,则跳转到登录页面的配置
shiroFilterFactoryBean.setLoginUrl("/login");
//登录成功默认跳转页面,不配置则跳转至"/"
shiroFilterFactoryBean.setSuccessUrl("");
//没有权限默认跳转的页面
shiroFilterFactoryBean.setUnauthorizedUrl("/error/403");
//自定义配置拦截器链,注意顺序(不同顺序有影响)--枚举名称字段请查看拦截规则
//Shiro验证URL时,URL匹配成功便不再继续匹配查找(所以要注意配置文件中的URL顺序,尤其在使用通配符时)
Map<String, String> map = new LinkedHashMap<>(16,1);
map.put("/logout", "logout");
map.put("/submit","anon");
map.put("/login", "anon");
map.put("/gifCode", "anon");
map.put("/css/**", "anon");
map.put("/fonts/**", "anon");
map.put("/img/**", "anon");
map.put("/js/**", "anon");
map.put("/lib/**", "anon");
//所有请求需要user认证
map.put("/**", "user");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
②DefaultWebSecurityManager
-
DefaultWebSecurityManager类主要定义了设置subjectDao,获取会话模式,设置会话模式,设置会话管理器,是否是http会话模式等操作,它继承了DefaultSecurityManager类,实现了WebSecurityManager接口
-
实例:
@Bean public DefaultWebSecurityManager getSecurityManager(){ DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager(); //setRealm(realm)->getUserRealm(HashedCredentialsMatcher matcher)方法详见下段代码 securityManager.setRealm(getUserRealm(hashedCredentialsMatcher())); return securityManager; }
③Realm(此处不详细介绍后面有关于Realm的详细介绍)
-
Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户 进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/ 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource
-
实例:
@Bean //UserRealm是由自己定义继承的AuthorizingRealm(继承后重写的方法详见Realm详解) public UserRealm getUserRealm(HashedCredentialsMatcher matcher){ UserRealm userRealm=new UserRealm(); userRealm.setCredentialsMatcher(matcher); return userRealm; }
④HashedCredentialsMatcher
-
Shiro 提供了用于加密密码和验证密码服务的 CredentialsMatcher 接口,而 HashedCredentialsMatcher 正是 CredentialsMatcher 的一个实现类。写项目的话,总归会用到用户密码的非对称加密,目前主流的非对称加密方式是 MD5 ,以及在 MD5 上的加盐处理,而 HashedCredentialsMatcher 也允许我们指定自己的算法和盐
-
实例:
@Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); //此处设置加密方式(SysConstant.ALGORITHNAME=MD5) credentialsMatcher.setHashAlgorithmName(SysConstant.ALGORITHNAME); //此处设置加密次数(SysConstant.HASHNUM=10) credentialsMatcher.setHashIterations(SysConstant.HASHNUM); return credentialsMatcher; }
⑤AuthorizationAttributeSourceAdvisor
-
开启Shiro的注解支持,比如:@RequireRoles @RequireUsers
-
实例:
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; }
Realm详解
-
Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户 进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/ 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource
-
Realm继承关系
一般继承 AuthorizingRealm(授权)即可;其继承了 AuthenticatingRealm(即身份验证),而且也间接继承了 CachingRealm(带有缓存实现),重写里面doGetAuthenticationInfo 认证和doGetAuthorizationInfo授权方法。
-
doGetAuthorizationInfo授权方法
-
授权过程:
-
实例:
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { /** * 1. 获取当前登录用户信息 * 2. 获取当前用户关联的角色、权限等 * 3. 将角色、权限封装到AuthorizationInfo对象中并返回 */ User user = (User) SecurityUtils.getSubject().getPrincipal(); SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo(); //以下注释代码主要是角色:权限控制 /** * //获取用户角色权限 * /** * for(PositionVO position : positions){ * //职位 * roleStrings.add(position.getName()); * for(PermissionVO permission : position.getPermissions()){ * //职位对应的权限 * permissionStrings.add(position.getName() + ":" + permission.getName()); * } * } * authorizationInfo.setRoles(roleStrings); * authorizationInfo.setStringPermissions(permissionStrings); */ //这里是主要对角色进行控制 List<Role> userRole = roleService.findUserRole(user.getId()); simpleAuthorizationInfo.addStringPermission(userRole.get(0).getRoleName()); return simpleAuthorizationInfo; }
-
doGetAuthenticationInfo认证方法(doGetAuthenticationInfo实现一些业务,例如判断用户是否锁定等等)
-
认证过程:
-
实例:
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //AuthenticationToken代表登录时使用的票据。在shiro中只有一种实现就UsernamePasswordToken if (!(authenticationToken instanceof UsernamePasswordToken)) { throw new UnknownAccountException(); } /** * 1. 从token中获取输入的用户名 * 2. 通过username查询数据库得到用户对象 * 3. 调用Authenticator进行密码校验 */ //获取用户名 String username= (String) authenticationToken.getPrincipal(); User byNameUser = userService.findByName(username); // Session session = SecurityUtils.getSubject().getSession(); //session.setAttribute(SESSION_LOGIN_CUSTOMER_ID, user.getId()); //可以用session将用户信息存在shiro的session当中,方便获取(存储在服务端)。JWT用户信息存于 token(客户端) if (byNameUser==null){ throw new UnknownAccountException(String.valueOf(UavStatesEnums.ACCOUNT_UNKNOWN.getMessage())); } /** * 交给Shiro进行密码的解密校验 * 调用SecurityUtils.getSubject().getPrincipal() 遇到类型转换问题,报错 String !=> User * 请参考这篇文章:{@link https://blog.csdn.net/qq_35981283/article/details/78634575} */ return new SimpleAuthenticationInfo(byNameUser,byNameUser.getPassword(), ByteSource.Util.bytes(byNameUser.getSalt()),getName()); }
登陆Controller
-
在登陆层可以做很多操作,这里只是实现了简单的登陆功能
-
实例:
public ResponseCode tologin(@RequestBody User user){ try { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token=new UsernamePasswordToken(user.getUsername(),user.getPassword()); subject.logout(); subject.login(token); return ResponseCode.success(super.getToken()); } catch (UnknownAccountException e){ return ResponseCode.accountunkonw(); }catch (IncorrectCredentialsException e){ return ResponseCode.usererror(); } }
总结
Shiro是一个功能很齐全的框架,使用起来也很容易,但是要想用好却有相当难度,希望如果用的不复杂就不要把Shiro代码结构写的太复杂,以上代码是Shiro简单实现登陆的整套代码,可以直接使用,本人才学疏浅,如有错误请指出。
-
总而言之:你不够优秀
Java-扫地僧 发布了14 篇原创文章 · 获赞 4 · 访问量 422 私信 关注