目录
- Shiro 的核心组件
- Shiro 认证流程
- Shiro 授权流程
- 单 Realm
- Shiro 登陆认证 SimpleAuthenticationInfo 对象
- 多 Realm
- ShiroConfig
- Shiro过滤器配置 ShiroFilterFactoryBean
- Shiro自定义过滤器
- Shiro 过滤器执行链路梳理
- 代码自取
- 层级结构
- Login.java
- BearerTokenRealm.java
- ShiroRealm.java
Shiro 的核心组件
Realm、SecurityManager、Subject
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,将按照相应的顺序及策略进行访问。
原文链接:https://blog.csdn.net/geejkse_seff/article/details/124345585
Shiro 授权流程
待学习
单 Realm
定义一个 Realm 类继承 AuthorizingRealm,重写 授权 和 认证 两个方法,实现用户的认证授权功能。参考 Demo 代码。
Shiro 登陆认证 SimpleAuthenticationInfo 对象
认证方法中,下面的代码怎么理解:
new SimpleAuthenticationInfo(user, user.getPasswd, getName());
Shiro 会把参数一作为 Subject 主体的信息,通过下面的方式获取:
UserSession user = (UserSession) subject.getPrincipal();
Shiro 会拿 参数二 和 AuthenticationToken 中的 getCredentials() 比较,判断密码的正确性。不匹配则抛出异常。
参考文章
多 Realm
实战:多端用户登入分别认证
ShiroConfig
定义各种 Shiro 配置的 Bean:SecurityManager、DefaultWebSessionManager、ShiroFilterFactoryBean
Shiro过滤器配置 ShiroFilterFactoryBean
一些写法 和 开发注意参考
Shiro自定义过滤器
ShiroFilterFactoryBena shiroFilter = new ShiroFilterFactoryBena();
Map<String, Filter> filter = new HashMap<>();
filters.put("authc", new MyFilter());
shiroFilter.setFilters(filter);
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/login", "anon");
filterMap.put("/**", "authc");
shiroFilter.setFilterChainDefinitionMap(filterMap);
实战,在过滤器中校验 Token
Shiro 过滤器执行链路梳理
用户登入的动作,Realm里的授权、认证动作,自定义过滤器里的动作,它们的先后顺序是怎么样的,有什么样的调用关系?
在 Shiro 的配置类中,我们通过 ShiroFilterFactoryBean 来添加过滤器,链路的梳理也要在 ShiroFilterFactoryBean 类里找答案。
Shiro过滤器执行链路梳理(认证和授权)
Shiro的Filter机制详解—源码分析
代码自取
只贴了一些核心代码,提供一些思路。
层级结构
Login.java
登入分为:第一次账号密码登入;之后 Token 登入
/**
* 登陆api
*/
@RestController
@Slf4j
public class LoginApi {
/**
* 登录
*
* @param username 用户名
* @param password 密码
* @return 成功信息
*/
@PostMapping("/login")
public ResponseDTO login(String username, String password) {
UsernamePasswordToken token = new UsernamePasswordToken(userId, EncrpytionUtil.encrypt(password));
Subject subject = SecurityUtils.getSubject();
subject.login(token);
UserSession userSession = (UserSession) subject.getPrincipal();
ResponseDTO loginResponse = new ResponseDTO();
loginResponse.setInfo(userSession.getInfo());
return loginResponse;
}
/**
* 登出系统
*
* @return ImsTeResponse
*/
@GetMapping("/logout")
public ResponseDTO logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
return new ResponseDTO().success();
}
}
BearerTokenRealm.java
/**
* BearerTokenRealm
*
*/
@Component
@Slf4j
public class BearerTokenRealm extends AuthorizingRealm {
/**
* ICommDictEntryAppService 字典实体服务
*/
@Autowired
private ICommDictEntryAppService dictEntryAppService;
/**
* 权限service
*/
@Autowired
private IRoleAppService roleAppService;
/**
* 员工service
*/
@Autowired
private IOperatorAppService operatorAppService;
/**
* 外部系统访问授权业务层接口
*/
private final ICommSystemAccessTokenAppService commSystemAccessTokenService;
/**
* 登录相关业务层接口
*/
private final ILoginAppService loginAppService;
/**
* 构造方法
*
* @param commSystemAccessTokenService 外部系统访问授权业务层接口
* @param loginAppService 登录相关业务层接口
*/
@Autowired
public BearerTokenRealm(ICommSystemAccessTokenAppService commSystemAccessTokenService,
ILoginAppService loginAppService) {
this.commSystemAccessTokenService = commSystemAccessTokenService;
this.loginAppService = loginAppService;
}
/**
* 授权
* @param authenticationToken
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
UserSession userSession = (UserSession) SecurityUtils.getSubject().getPrincipal();
Long operatorId = userSession.getOperatorId();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
List<CommRoleResponseDTO> roleList = roleAppService.findOperatorRole(operatorId);
Set<String> roleSet = roleList.stream().map(CommRoleResponseDTO::getRoleName).collect(Collectors.toSet());
simpleAuthorizationInfo.setRoles(roleSet);
return simpleAuthorizationInfo;
}
/**
* 认证
* @param authenticationToken
* @return
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
UserSession userSession = (UserSession) SecurityUtils.getSubject().getPrincipal();
if (userSession == null) {
throw new MercuryException("用户未登入或Token已过期", 202);
}
String token = ((BearerToken) authenticationToken).getToken();
UserAuthInfo userAuthInfo;
try {
userAuthInfo = new Gson().fromJson(TextCodec.BASE64URL.decodeToString(token),
UserAuthInfo.class);
} catch (JsonSyntaxException e) {
throw new AuthenticationException("非法请求");
}
Calendar minTime = Calendar.getInstance();
minTime.add(Calendar.MINUTE, -15);
Calendar maxTime = Calendar.getInstance();
maxTime.add(Calendar.MINUTE, 15);
if (userAuthInfo.get_() < minTime.getTimeInMillis()
|| userAuthInfo.get_() > maxTime.getTimeInMillis()) {
throw new MercuryException("非法请求,token过期", 201);
}
String userName = userAuthInfo.getUserName();
OperatorPO operator = operatorAppService.findByUserId(userName);
if (operator == null) {
throw new AuthenticationException("用户不存在!");
}
return new SimpleAuthenticationInfo(loginAppService.buildSessionOperator(operator), token, getName());
}
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof BearerToken;
}
}
ShiroRealm.java
/**
* shirorealm
*/
@Slf4j
public class ShiroRealm extends AuthorizingRealm {
/**
* 登录相关业务层接口
*/
@Autowired
private IOperatorAppService operatorAppService;
/**
* 登录相关业务层接口
*/
@Autowired
private ILoginAppService loginAppService;
/**
* 权限service
*/
@Autowired
private IRoleAppService roleAppService;
/**
*
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
UserSession userSession = (UserSession) SecurityUtils.getSubject().getPrincipal();
Long operatorId = userSession.getOperatorId();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
List<CommRoleResponseDTO> roleList = roleAppService.findOperatorRole(operatorId);
Set<String> roleSet = roleList.stream().map(CommRoleResponseDTO::getRoleName).collect(Collectors.toSet());
simpleAuthorizationInfo.setRoles(roleSet);
return simpleAuthorizationInfo;
}
/**
* 认证
* @param authenticationToken
* @return
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
try {
String userId = (String) authenticationToken.getPrincipal();
String principal = String.valueOf((char[])authenticationToken.getCredentials());
OperatorPO operator = operatorAppService.findById(userId);
if (operator == null) {
log.error("用户不存在!");
throw new IncorrectCredentialsException("用户不存在!");
}
String passwd = "从数据库获取的用户密码";
UserSession userSession = loginAppService.buildSessionOperator(operator);
//new SimpleAuthenticationInfo(arg1,arg2,arg3):第一个参数是(UserSession) subject.getPrincipal()的信息;第二个参数,应该传数据库的密码,Shiro会拿 passwd 和 principal比较校验密码;参数三是当前Realm的名字
return new SimpleAuthenticationInfo(userSession, passwd, getName());
} catch (IncorrectCredentialsException e) {
throw new IncorrectCredentialsException("密码或用户名错误!");
}
}
//重写supports()方法,可以在多个 Realm 场景里使用,见 CustomModularRealmAuthenticator.java
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
}