目录
- 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 字典实体服务*/@Autowiredprivate ICommDictEntryAppService dictEntryAppService;/*** 权限service*/@Autowiredprivate IRoleAppService roleAppService;/*** 员工service*/@Autowiredprivate IOperatorAppService operatorAppService;/*** 外部系统访问授权业务层接口*/private final ICommSystemAccessTokenAppService commSystemAccessTokenService;/*** 登录相关业务层接口*/private final ILoginAppService loginAppService;/*** 构造方法** @param commSystemAccessTokenService 外部系统访问授权业务层接口* @param loginAppService 登录相关业务层接口*/@Autowiredpublic BearerTokenRealm(ICommSystemAccessTokenAppService commSystemAccessTokenService,ILoginAppService loginAppService) {this.commSystemAccessTokenService = commSystemAccessTokenService;this.loginAppService = loginAppService;}/*** 授权* @param authenticationToken* @return*/@Overrideprotected 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*/@Overrideprotected 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());}@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof BearerToken;}
}
ShiroRealm.java
/*** shirorealm*/
@Slf4j
public class ShiroRealm extends AuthorizingRealm {/*** 登录相关业务层接口*/@Autowiredprivate IOperatorAppService operatorAppService;/*** 登录相关业务层接口*/@Autowiredprivate ILoginAppService loginAppService;/*** 权限service*/@Autowiredprivate IRoleAppService roleAppService;/*** * 授权* @param principalCollection* @return*/@Overrideprotected 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*/@Overrideprotected 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@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof UsernamePasswordToken;}}