基础概念
1、UsernamePasswordToken,用来封装用户登录信息
public UsernamePasswordToken(final String username, final char[] password,
final boolean rememberMe, final String host) {
this.username = username;
this.password = password;
this.rememberMe = rememberMe;
this.host = host;
}
2、 SecurityManager,Shiro 的核心部分,负责安全认证与授权。
3、Subject,Shiro 的一个抽象概念,包含了用户信息
Subject currentUser = SecurityUtils.getSubject();
if (currentUser.isAuthenticated()) {
UserBO userBO = (UserBO) currentUser.getPrincipal();
if (null != userBO) {
return userBO;
}
}
4、Realm,根据项目的需求,验证doGetAuthorizationInfo和授权doGetAuthenticationInfo的逻辑在 Realm 中实现。
5、AuthenticationInfo,用户的角色信息集合,认证时使用。
new SimpleAuthenticationInfo(user, user.getPassword(), getName());
6、AuthorizationInfo,角色的权限信息集合(当前用户有哪些角色,可以访问哪个uri),授权时使用。
7、DefaultWebSecurityManager,安全管理器,开发者自定义的 Realm 需要注入到 DefaultWebSecurityManager 进行管理才能生效
@Bean("customSecurityManager")
DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
defaultSecurityManager.setRealm(realm());
defaultSecurityManager.setSessionManager(sessionManager());
return defaultSecurityManager;
}
8、ShiroFilterFactoryBean,Shiro 的基本运行机制是开发者定制规则(Filter)Shiro去执行
shiroFilterFactoryBean.setFilterChainDefinitionMap( shiroService.loadFilterChainDefinitionMap());
首先shiro独立于容器,因为需要引用容器内部的类,所以先将shiro对应的配置类加入到容器,具体实现ApplicationContextAware接口,重写方法
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Realm userRealm = applicationContext.getBean(WesRealm.class);
DefaultWebSecurityManager defaultWebSecurityManager = (DefaultWebSecurityManager) applicationContext.getBean(SecurityManager.class);
defaultWebSecurityManager.setRealm(userRealm);
}
shiro如何获取当前用户信息和验证,这就用到了我们的realm,简单的realm只是给类,要想被shiro这个大家庭接受,需要“自降”身价,认AuthorizingRealm为父(这也是普通类的福分了,毕竟地位一下子不一样了)
获取当前用户的角色和资源
/**
* 获取当前用户有哪些角色给SimpleAuthorizationInfo的roles
* 获取当前用户可访问哪些资源给SimpleAuthorizationInfo的stringPermissions
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
}
if (principals.getPrimaryPrincipal() instanceof UserBO) {
UserBO userBO = (UserBO) principals.getPrimaryPrincipal();
List<String> userUriList = userService.queryUrlListByUserId(userBO.getUserID());
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
List<String> roleIdList = userService.getRoleIdListByUserId(userBO.getUserID());
if (CollectionUtil.isNotEmpty(roleIdList)) {
authorizationInfo.addRoles(roleIdList);
}
for (String uri : userUriList) {
authorizationInfo.addStringPermission(uri);
}
return authorizationInfo;
}
return null;
}
验证当前用户密码等
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//该参数是是登录时UsernamePasswordToken的值
WesUserToken wesUserToken = (WesUserToken) authenticationToken;
if (wesUserToken.getUsername() == null) {
throw new AccountException("Null usernames are not allowed by this realm.");
}
//可判断用户是否存在,密码是否正确
UserBO user = userService.getUserWithTokenByName(wesUserToken.getUsername());
if (user == null) {
throw new UnknownAccountException("No account found for admin [" + wesUserToken.getUsername() + "]");
}
//将user赋值给SimpleAuthenticationInfo,方便权限判断时获取,要不怎么知道当前是谁
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
if (user.getSalt() != null) {
//加盐,这块根据项目需要
authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(user.getSalt()));
}
return authenticationInfo;
}
具体看下AuthenticationToken参数
public class WesUserToken extends UsernamePasswordToken {
//登录时赋值currentUser.login(new WesUserToken(userBO.getUserName(), userBO.getPassword()));
public WesUserToken(final String username, final String password) {
super(username, password.toCharArray(), false, null);
}
}
当前realm还是比较独立的,没有归为shiro,如何处理呢?再shiro的配置类中,将其设为bean并绑给DefaultWebSecurityManager
@Bean("customSecurityManager")
DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
defaultSecurityManager.setRealm(realm());
return defaultSecurityManager;
}
@Bean
public Realm realm() {
WesRealm realm = new WesRealm();
return realm;
}
这样用户的信息补充完了,具体整个系统我们要拦截哪些资源呐?配置filter
/**
* 以下参数也可以new,我习惯使用bean
* @param shiroService 自己的具体校验哪些url的配置及其更新
* @param tokenCheckFilter 自定义的过滤规则
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(
@Qualifier("shiroService") ShiroService shiroService,
@Qualifier("tokenCheckFilter") TokenCheckFilter tokenCheckFilter,
@Qualifier("customSecurityManager") SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, Filter> filtersMap = new LinkedHashMap<>();
// 定义过滤器名称,注意这个"token"后面要考 【注:map里面key值对于的value要为authc才能使用自定义的过滤器】
filtersMap.put("token", tokenCheckFilter);
//filtersMap.put("corn", myFormAuthenticationFilter);
shiroFilterFactoryBean.setFilters(filtersMap);
//登录url,这个前后端分离其实是不需要,具体后面再说
shiroFilterFactoryBean.setLoginUrl("/wes/passport/account/unlogin");
shiroFilterFactoryBean.setFilterChainDefinitionMap( shiroService.loadFilterChainDefinitionMap());
return shiroFilterFactoryBean;
}
@Bean
public ShiroService shiroService() {
return new ShiroServiceImpl();
}
@Bean
public TokenCheckFilter tokenCheckFilter(){
return new TokenCheckFilter();
}
具体的拦截什么?
@Override
public Map<String, String> loadFilterChainDefinitionMap(){
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
List<AuthView> list = authViewService.getAll();
if (CollectionUtil.isNotEmpty(list)) {
Map<Integer, List<AuthView>> viewTypeMap = list.stream().collect(Collectors.groupingBy(AuthView::getType));
List<String> buttonList = viewTypeMap.getOrDefault(2, new ArrayList<>()).stream().map(AuthView::getUrl).collect(Collectors.toList());
for (String viewUrl : buttonList) {
String[] urlNum = viewUrl.split(StrUtil.COLON);
filterChainDefinitionMap.put(urlNum[0], "authc,token");
}
/…………逻辑同上,注意这个filterChainDefinitionMap.put的先后顺序,先上后下
}
filterChainDefinitionMap.put("/**", "anon");
return filterChainDefinitionMap;
}
如何拦截?看我们“token” filtersMap.put("token", tokenCheckFilter);经测试要继承FormAuthenticationFilter哦,这块跟具体的业务逻辑有很强的关系
/**
* 判断是否拥有权限 true:认证成功 false:认证失败
* mappedValue 访问该url时需要的权限
* subject.isPermitted 判断访问的用户是否拥有mappedValue权限
*/
@Override
public boolean isAccessAllowed(ServletRequest httpRequest, ServletResponse response, Object mappedValue) {
HttpServletRequest request = (HttpServletRequest) httpRequest;
String requestUri = request.getRequestURI();
requestUri = requestUri.replaceAll(DOUBLE_SLASH, SINGLE_SLASH);
UserTokenCheck paramUser = getInfoFromCookie(request.getCookies());
log.info("拦截器 当前{} user{}", requestUri, paramUser);
if (interceptorUtil.checkParamSimple(paramUser)) {
interceptorUtil.login(paramUser.getUserId());
return true;
}
List<AuthView> list = authViewService.getAll();
if (CollectionUtil.isNotEmpty(list)) {
Map<Integer, List<AuthView>> viewTypeMap = list.stream().collect(Collectors.groupingBy(AuthView::getType));
List<String> allowList = viewTypeMap.getOrDefault(0, new ArrayList<>()).stream().map(AuthView::getUrl).collect(Collectors.toList());
if (CollectionUtil.isNotEmpty(allowList) && interceptorUtil.checkContainsUri(requestUri, allowList)) {
interceptorUtil.login(paramUser.getUserId());
return true;
}
return true;
}
当验证失败是,覆盖父类发方法,该部分与业务强相关,由于历史原因以下处理方式只能流泪,注释调的那套处理方式是比较推荐滴
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
Subject subject = getSubject(request, response);
// If the subject isn't identified, redirect to login URL
if (subject.getPrincipal() == null) {
//跳转至登录页
//saveRequestAndRedirectToLogin(request, response);
throw ErrorCodes.ERROR_COOKIE_MISS.exception();
} else {
//给前端提示无接口访问权限的错误码
//saveRequestAndReturnApiAccessError(request, response);
throw ErrorCodes.ERROR_ROLE_NOT_RIGHT.exception();
}
//return true;
}
@Override
protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
// JSONObject jsonObject = new JSONObject();
// jsonObject.put("returnCode", "1000005");
// jsonObject.put("returnUserMsg", "登陆时间过长,请重新登陆");
// try {
// flushMsgStrToClient(response, jsonObject);
// } catch (ServletException e) {
// e.printStackTrace();
// }
throw ErrorCodes.ERROR_ROLE_NOT_RIGHT.exception();
}
public static void flushMsgStrToClient(ServletResponse response, Object object)
throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSONObject.toJSONString(object));
response.getWriter().flush();
}
protected void saveRequestAndReturnApiAccessError(ServletRequest request, ServletResponse response) {
saveRequest(request);
JSONObject jsonObject = new JSONObject();
jsonObject.put("returnCode", "1000001");
jsonObject.put("returnUserMsg", "无权限请求对应api接口");
try {
flushMsgStrToClient(response, jsonObject);
} catch (Exception e) {
e.printStackTrace();
}
}
先这样done