1.登录
// 1 从Shiro框架中,获取一个Subject对象,代表当前会话的用户比如(com.shkj.financial.service.shiro.realm.ShiroAuthorizingRealm_0)
Subject subject = SecurityUtils.getSubject();
// 2 认证subject的身份
// 2.1 封装要认证的身份信息(用前端传来的用户名和密码封装成AuthenticationToken,在下面会说为什么)
AuthenticationToken token = new UsernamePasswordToken(account.getUsername(),
EncryptUtil.getMD5Str(account.getPassword()));
// 2.2 认证:当前会话对象调用login方法
subject.login(token);
调用subject.login(token);最后会进入AuthorizingRealm 的doGetAuthenticationInfo方法中进行登录认证,在上面的token中封装的username就在下面用到了。
@Component("shiroRealm")
public class ShiroAuthorizingRealm extends AuthorizingRealm {
//注入自己的service层
@Autowired
private IUserService userService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//从authenticationToken也就是登录用UsernamePasswordToken封装的token中拿出
//前端传来的用户名 可以点authenticationToken进入UsernamePasswordToken源码中了解为什么可以拿到
String username = authenticationToken.getPrincipal().toString();
//根据用户名查询该用户的信息
AuthSysUser user = this.userService.findByUserName(username);
// 用AuthenticationInfo对象,封装username和password
ShiroUser shiroUser = new ShiroUser();
String password = user.getUrPassword();
shiroUser.setId(user.getUrId());
shiroUser.setUserName(user.getUrUserName());
shiroUser.setAuthCacheKey(user.getUrUserName() + user.getUrId());
shiroUser.setPassword(password);
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(shiroUser, password, this.getName());
//return时会触发密码验证
return info;
}
}
想了解密码是何时怎样校验的可以看这位博主shiro密码校验
在登录校验成功之后会进行获取身份信息
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection)
{
//授权资源检查
// 获取主身份
String username = principalCollection.getPrimaryPrincipal().toString();
ShiroUser shiroUser = (ShiroUser) principalCollection.getPrimaryPrincipal();
//获取数据库中的用户角色对应的权限
String username = shiroUser.getUserName();
List<String> permissionList = this.userService.getUserPermssions(username);
//创建授权对象
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//将当前用户权限设置给授权对象 permissionList放置的是order/create/*
simpleAuthorizationInfo.addStringPermissions(permissionList);
return simpleAuthorizationInfo;
}
细粒度权限控制
注解式的权限控制需要配置两个Bean,第一个是AdvisorAutoProxyCreator,代理生成器,需要借助SpringAOP来扫描@RequiresRoles和@RequiresPermissions等注解,生成代理类实现功能增强,从而实现权限控制。需要配合AuthorizationAttributeSourceAdvisor一起使用,否则权限注解无效,配置的DefaultAdvisorAutoProxyCreator相当于一个切面,下面这个类就相当于切点了,两个一起才能实现注解权限控制
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
autoProxyCreator.setProxyTargetClass(true);
return autoProxyCreator;
}
权限注解
@RequiresAuthentication
表示当前 Subject 已经通过 login 进行了身份验证;即 Subject.isAuthenticated() 返回 true。
@RequiresUser
表示当前 Subject 已经身份验证或者通过记住我登录的。
@RequiresGuest
表示当前 Subject 没有身份验证或通过记住我登录过,即是游客身份。
@RequiresRoles(value={“admin”, “user”}, logical= Logical.AND) logical是枚举类 有and和or
表示当前 Subject 需要角色 admin 和 user。
@RequiresPermissions (value={"sys:user:list", "sys:role:list"}, logical= Logical.OR)
表示当前 Subject 需要权限 sys:user:list或 sys:role:list(资源访问权限可以和后端访问路径完美匹配)可以将用户权限做缓存。
数据库表设计
为了简单化前后端的细粒度的权限控制将权限表和资源表进行合并 将权限直接定义为sys:user:list类型
基于源码的登录流程在shiro框架中获取到subject对象后调用subject的login方法需要传入AuthenticationToken,UsernamePasswordToken实现了AuthenticationToken,调用UsernamePasswordToken的构造方法传入账号和加密后的密码,DelegatingSubject实现了Subject
实现了login方法调用securityManager中的login方法,最后在AbstractAuthenticator的authenticate
方法,然后调用ModularRealmAuthenticator的doAuthenticate,会根据你AuthenticatingRealm的Realms的数量判断是简单登录还是复杂登录,AuthorizingRealm(授权) AuthenticatingRealm(认证)
AuthorizingRealm extends AuthenticatingRealm 所以我们自定义实现AuthorizingRealm中的doGetAuthorizationInfo(授权)和doGetAuthenticationInfo(认证) 就可以完成自己的认证和授权。
shiro中出现的问题
前后端分离后用shiro自定义的session,前端是获取不到的需要自定义DefaultWebSessionManager用其getSessionId方法将sessionid拿出来重写放到response请求头上。
方法一 自定义DefaultWebSessionManager解决
方法二 跨域方式解决问题
跨域解决的两种方式