litemall源码阅读4.04 litemall-admin-api的权限控制与安全配置

最近在这里卡了好久了。litemall的权限控制使用的是shrio。主要阅读了这篇文章这篇文章

在这里至少有四个文件与shrio配置相关:

src/main/java/org/linlinjava/litemall/admin/config/ShiroConfig.java

src/main/java/org/linlinjava/litemall/admin/config/ShiroExceptionHandler.java

src/main/java/org/linlinjava/litemall/admin/shiro/AdminAuthorizingRealm.java

src/main/java/org/linlinjava/litemall/admin/shiro/AdminWebSessionManager.java

Shiro是主要配置类,@Configuration在某种意义上就是将这个类转化为一个spring的bean配置类。

在这个类里主要定义了Shiro工作需要的bean。

@Configuration
public class ShiroConfig {

@Bean
public Realm realm() {
return new AdminAuthorizingRealm();  //返回认证和授权的的自定义realm
}

   //Filter工厂,设置对应的过滤条件和跳转条件
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
filterChainDefinitionMap.put("/admin/auth/login", "anon");//不会拦截匿名用户访问登录页
filterChainDefinitionMap.put("/admin/auth/401", "anon");//不会拦截匿名用户访问401
filterChainDefinitionMap.put("/admin/auth/index", "anon");//不会拦截匿名用户访问index
filterChainDefinitionMap.put("/admin/auth/403", "anon");//不会拦截匿名用户访问403
filterChainDefinitionMap.put("/admin/index/*", "anon");//不会拦截匿名用户访问/admin/index下的任意内容

filterChainDefinitionMap.put("/admin/**", "authc");//不会拦截已经登录的用户访问/admin下的内容
shiroFilterFactoryBean.setLoginUrl("/admin/auth/401");//登录成功后的url跳转
shiroFilterFactoryBean.setSuccessUrl("/admin/auth/index");//登录成功后的url跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/admin/auth/403");//登录失败后的url跳转
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);//设置拦截链
return shiroFilterFactoryBean;
}
  //返回自定义的sessionmanager。也就是AdminWebSessionManager.java中定义的对象
@Bean
public SessionManager sessionManager() {

return new AdminWebSessionManager();
}
  //返回securitymanager,配置了realm与sessionmanager。
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm());
securityManager.setSessionManager(sessionManager());
return securityManager;
}
  //开启shrio注解的配置
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
  //开启shrio注解的配置
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public static DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
}

接下来是自定义realm了。realm主要有两个功能,1,认证,2授权。

类内部有3个bean。

@Autowired
private LitemallAdminService adminService;  //管理员信息表。用于保存管理员的信息,比如管理员的角色,密码,头像等。
@Autowired
private LitemallRoleService roleService;  //角色信息表,用于将管理员分为多种角色。
@Autowired
private LitemallPermissionService permissionService;  //角色权限表,用于保存每种角色分别有什么权限。

先看认证返回函数。这个函数会在后台的src/main/java/org/linlinjava/litemall/admin/web/AdminAuthController.java文件调用

currentUser.login(new UsernamePasswordToken(username, password));

时调用。

 

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  //upToken中保存了登录的用户名和密码
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
String password = new String(upToken.getPassword());
  //异常处理,在src/main/java/org/linlinjava/litemall/admin/config/ShiroExceptionHandler.java中,下面会讲
if (StringUtils.isEmpty(username)) {
throw new AccountException("用户名不能为空");
}
if (StringUtils.isEmpty(password)) {
throw new AccountException("密码不能为空");
}
  //使用adminservice获取当前username管理员账户信息。
List<LitemallAdmin> adminList = adminService.findAdmin(username);
Assert.state(adminList.size() < 2, "同一个用户名存在两个账户");
if (adminList.size() == 0) {
throw new UnknownAccountException("找不到用户(" + username + ")的帐号信息");
}
LitemallAdmin admin = adminList.get(0);
  //使用加盐加密算法查看用户名与密码是否与数据库中的数据匹配
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
if (!encoder.matches(password, admin.getPassword())) {
throw new UnknownAccountException("找不到用户(" + username + ")的帐号信息");
}
  //如果一切成功则返回一个新的认证信息。
return new SimpleAuthenticationInfo(admin, password, getName());
}

接下来看授权,授权信息会在前端点击url时进行调用。以确定是否有权限进行操作。

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
}
  //获取当前操作的用户信息
LitemallAdmin admin = (LitemallAdmin) getAvailablePrincipal(principals);
  //获取角色信息。
Integer[] roleIds = admin.getRoleIds();
  //根据角色信息表和角色权限表,返回当前用户的操作权限。
Set<String> roles = roleService.queryByIds(roleIds);
Set<String> permissions = permissionService.queryByRoleIds(roleIds);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(roles);
info.setStringPermissions(permissions);
return info;
}

之后我们看src/main/java/org/linlinjava/litemall/admin/shiro/AdminWebSessionManager.java

首次登陆的时候,函数getSessionId会在进行上面realm验证前进行调用。因为是第一次登陆,所以这时候sessionId为空值。

如果登陆成功,则src/main/java/org/linlinjava/litemall/admin/web/AdminAuthController.java会返回登陆后的sessionId值,

 

 当第二次登陆,就可以获取到相应的LOGIN_TOKEN_KEY了。再将该值返回,用于服务器匹配当前session的状态。

public static final String LOGIN_TOKEN_KEY = "X-Litemall-Admin-Token";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";

@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getHeader(LOGIN_TOKEN_KEY);
if (!StringUtils.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
return super.getSessionId(request, response);
}
}

最后是src/main/java/org/linlinjava/litemall/admin/config/ShiroExceptionHandler.java

这个文件的知识点属于springMVC的内容,就是设置一个全局的异常拦截器。但这个文件里只拦截了Shiro抛出的异常。

比如,当用户的session过期或者用户无相应的页面权限,抛出的异常就会被该文件拦截到。

//处理登录异常
@ExceptionHandler(AuthenticationException.class)
@ResponseBody
public Object unauthenticatedHandler(AuthenticationException e) {
logger.warn(e.getMessage(), e);
return ResponseUtil.unlogin();
}
//处理权限异常
@ExceptionHandler(AuthorizationException.class)
@ResponseBody
public Object unauthorizedHandler(AuthorizationException e) {
logger.warn(e.getMessage(), e);
return ResponseUtil.unauthz();
}

 


 

posted @ 2020-12-12 10:42  961493347  阅读(401)  评论(0编辑  收藏  举报