Apache Shiro是一个强大且易用的Java安全框架,用于执行身份验证、授权、密码和会话管理,无论什么时候,用户认证和权限控制都是一个永恒的话题,前后端分离是当前很火的一种开发模式,便于前、后端人员专注于自己的逻辑实现,也更有利于大规模协作开发
- 前端:任何一种前端框架(如Vue、Angular等)
- 后端:springboot
二、主要逻辑
- 用户通过账号密码(身份+凭证)登录网站(前端页面调用后端登录接口,传入身份和凭证)
- 后端程序登录,并生成对应token给前端网页(token和用户id绑定并入库)
- 前端网页后续访问均带上token
- 后端通过token识别用户,并获得用户的访问权限
- 后端根据用户访问权限决定是否放行
shiro认证、授权序列图
三、对上图解释说明
- shiro最核心的两个过程为认证和授权,认证为识别用户的过程,授权为判断用户是否有权访问的过程
- 上图中的认证Filter要继承AuthenticatingFilter,并override如下方法:
(1)createToken
(2)isAccessAllowed---本方法直接返回false,即交给onAccessDenied方法处理
(3)onAccessDenied---调用executeLogin进行登录
public class OAuth2Filter extends AuthenticatingFilter { Logger logger = LoggerFactory.getLogger(this.getClass()); @Override protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception { //获取请求token String token = getRequestToken((HttpServletRequest) request); if (StringUtils.isBlank(token)) { return null; } return new OAuth2Token(token); } @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { //仅为“option”时返回true if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) { return true; } return false; } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { //获取请求token,如果token不存在,直接返回401 String token = getRequestToken((HttpServletRequest) request); if (StringUtils.isBlank(token)) { HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin()); String json = JSON.toJSONString(R.error(HttpStatus.SC_UNAUTHORIZED, "invalid token")); httpResponse.getWriter().print(json); return false; } return executeLogin(request, response); }......}
3、自定义Realm需要继承AuthorizingRealm,并overide如下方法:
(1)supports---需要支持上述Filter的createToken方法中返回的类型
(2)doGetAuthenticationInfo --- 返回 SimpleAuthenticationInfo(user, token, realmName)
(3)doGetAuthorizationInfo
@Componentpublic class OAuth2Realm extends AuthorizingRealm { @Autowired private ShiroService shiroService; @Override public boolean supports(AuthenticationToken token) { return token instanceof OAuth2Token; } /* * * 授权(验证权限时调用) */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SysUserEntity user = (SysUserEntity) principals.getPrimaryPrincipal(); Long userId = user.getUserId(); //用户权限列表 Set permsSet = shiroService.getUserPermissions(userId); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setStringPermissions(permsSet); return info; } /** * 认证(登录时调用) */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String accessToken = (String) token.getPrincipal(); //根据accessToken查询用户信息 SysUserTokenEntity tokenEntity = shiroService.queryByToken(accessToken); //token失效 if (tokenEntity == null || tokenEntity.getExpireTime().getTime() < System.currentTimeMillis()) { throw new IncorrectCredentialsException("token失效,请重新登录"); } //查询用户信息 SysUserEntity user = shiroService.queryUser(tokenEntity.getUserId()); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, accessToken, getName()); return info; }}
4、上述Token管理器需要自定义Token的实现,需要继承AuthenticationToken,并override如下方法:
(1)getPrincipal---返回身份,此方案直接返回token字符串
(2)getCredentials---返回凭证,此方法直接返回token字符串
public class OAuth2Token implements AuthenticationToken { private static final long serialVersionUID = 1L; private String token; public OAuth2Token(String token) { this.token = token; } @Override public String getPrincipal() { return token; } @Override public Object getCredentials() { return token; }}