题目描述
一个JavaWeb的项目,前后端分为member和admin进行登录,要分别拥有权限设定.
现在已经弄好了权限五表(总共10个表,member和admin各五张).
在配置不同用户登录时不使用一个路径登录,而是member通过/member/login, admin通过/admin/login.
本人对shiro不是很熟悉,接触了一两天,现在还在看zhangkaitao的shiro-example,
感觉其中的多Realm即ModularRealmAuthenticator类并不是我想的那种,其只能根据账号去寻找多个Realm.
看了一些csdn的博客也是各有各的写法,无非是在login页面增加字段loginType,再封装AuthenticationToken,
通过loginType来调用realm.但我的登录属于两个页面,还需要根据不同的用户类型来返回不同的路径拦截.
除了对Realm进行判定,还需要返回不同的路径拦截器,这个还没有想法.
有大神提点一下可好...不然只能等自己解决后上传答案了.
题目来源及自己的思路
重写DefaultFilterChainManager或是PathMatchingFilter,根据用户的不同添加不同的拦截.
实例化ShiroFilterFactoryBean根据不同设置.
相关代码
// 请把代码文本粘贴到下方(请勿用图片代替代码)
现在的MyRealm配置了两种用户的获取信息:
package com.tansuo365.test1.realm;
import com.tansuo365.test1.bean.Member;
import com.tansuo365.test1.bean.User;
import com.tansuo365.test1.entity.MyLoginInstance;
import com.tansuo365.test1.service.*;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.pam.AuthenticationStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Set;
@Component
public class MyRealm extends AuthorizingRealm {
@Autowired
private MemberService memberService;//member
@Autowired
private UserService userService;//user
@Autowired
private MroleService mroleService;//member
@Autowired
private RoleService roleService; //user
@Autowired
private MemberPermissionService memberPermissionService;//member
@Autowired
private PermissionService permissionService;//user
/*获取授权权限信息*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info = null;
//获取当前登录类型
String loginType = (String)SecurityUtils.getSubject().getSession().getAttribute("loginType");
Set<String> permissions = null;
Set<String> roles = null;
// 能进入到这里,表示账号已经通过验证了
String username = (String)principals.getPrimaryPrincipal();
if(LoginEnum.ADMIN.toString().equals(loginType)){
permissions = permissionService.listPermissions(username);
roles = roleService.listRoleNames(username);
}
if(LoginEnum.MEMBER.toString().equals(loginType)){
// Member member = memberService.getByName(username);
// 通过service获取角色和权限
permissions = memberPermissionService.listMemberPermissions(username);
roles = mroleService.listMroleNames(username);
}
// 授权对象
info = new SimpleAuthorizationInfo();
// 把通过service获取到的角色和权限放进去
info.setStringPermissions(permissions);
info.setRoles(roles);
return info;
}
/*获取身份验证信息*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
User user = null;//后台用户
Member member = null;//前端会员
MyLoginInstance instance = null;//封装了几种用户的通用方法
//1.把AuthenticationToken转换为CustomizedToken
CustomizedToken customizedToken = (CustomizedToken)token;
//2.从CustomizedToken中获取memberName
String nameInInput = customizedToken.getUsername();
//3.若用户不存在,抛出UnknownAccountException异常
String loginType = customizedToken.getLoginType();
if(LoginEnum.ADMIN.toString().equals(loginType)){
user = userService.getByName(nameInInput);
if(null == user){
throw new UnknownAccountException("管理员不存在!");
}
}
if(LoginEnum.MEMBER.toString().equals(loginType)){
member = memberService.getByName(nameInInput);
if(null == member){
throw new UnknownAccountException("会员不存在!");
}
}
//4.根据用户的情况,来构建AuthenticationInfo对象并返回
//通常使用的实现类为SimpleAuthenticationInfo
Object principal = instance.getInstanceName();
Object credentials = instance.getInstancePassword();
String salt = instance.getInstanceSalt();
String realmName = getName();
ByteSource credentialsSalt = ByteSource.Util.bytes(salt);
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal,credentials,credentialsSalt,realmName);
return info;
}
}
但是问题是路径还没实现可调整化:
只有一个admin的:
package com.tansuo365.test1.filter;
import com.tansuo365.test1.service.PermissionService;
import com.tansuo365.test1.util.SpringContextUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.PathMatchingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.Set;
/*URL路径匹配过滤器*/
public class URLPathMatchingFilter extends PathMatchingFilter {
@Autowired
private PermissionService permissionService;
/*前置处理器*/
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue)
throws Exception {
if(null==permissionService)
permissionService = SpringContextUtils.getContext().getBean(PermissionService.class);
String requestURI = getPathWithinApplication(request);
System.out.println("requestURI:" + requestURI);
Subject subject = SecurityUtils.getSubject();
// 如果没有登录,就跳转到登录页面 --
if (!subject.isAuthenticated()) {
WebUtils.issueRedirect(request, response, "/admin/login");
return false;
}
// 看看这个路径权限里有没有维护,如果没有维护,一律放行(也可以改为一律不放行)
System.out.println("permissionService:"+permissionService);
boolean needInterceptor = permissionService.needInterceptor(requestURI);
if (!needInterceptor) {
return true;
} else {
boolean hasPermission = false;
String userName = subject.getPrincipal().toString();
Set<String> permissionUrls = permissionService.listPermissionURLs(userName);
for (String url : permissionUrls) {
// 这就表示当前用户有这个权限
if (url.equals(requestURI)) {
hasPermission = true;
break;
}
}
if (hasPermission)
return true;
else {
UnauthorizedException ex = new UnauthorizedException("当前用户没有访问路径 " + requestURI + " 的权限");
subject.getSession().setAttribute("ex", ex);
WebUtils.issueRedirect(request, response, "/admin/unauthorized");
return false;
}
}
}
}
ShiroConfiguration也是只有admin的:
package com.tansuo365.test1.shiro;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.tansuo365.test1.filter.URLPathMatchingFilter;
import com.tansuo365.test1.realm.AdminRealm;
import com.tansuo365.test1.realm.MyRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
//import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
/*权限配置类*/
@Configuration
public class ShiroConfiguration {
// @Autowired
// private MemberShiroConfiguration memberShiroConfiguration;
//用于thymeleaf模板使用shiro标签
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
@Bean
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在
* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
* <p>
* Filter Chain定义说明
* 1、一个URL可以配置多个Filter,使用逗号分隔
* 2、当设置多个过滤器时,全部验证通过,才视为通过
* 3、部分过滤器可指定参数,如perms,roles
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//TODO 2019-1-29 17:40
//将设置url的参数改为application.properties中去配置
//securityManager改为可选择的进行传入
//customisedFilter的hashmap传入的url及URLPath...Filter改为参数可传入
//customisedFilter这个HashMap装配了登录及登录后可未授权页面(admin的配置admin的,member的配置member的)
//filterChainDefinitionMap
//1.securityManager中的realm按需配置
//2.shiroFilterFactoryBean中的set...Url为按需配置
//3.shiroFilterFactoryBean.setFilters(customisedFilter);中的customisedFilter按需配置
//**customisedFilter为put了仅仅一个"url",value为URL路径匹配过滤器(按需传入)
//4.shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
//**filterChainDefinitionMap为按需配置
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/admin/login"); //原为/login
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/admin/"); //原为/index
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/admin/unauthorized"); //原为/unauthorized
//拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//自定义拦截器
Map<String, Filter> customisedFilter = new HashMap<>();
customisedFilter.put("url", getURLPathMatchingFilter());
//配置映射关系 anon表示不需要权限即可访问
filterChainDefinitionMap.put("/admin/login", "anon");
// filterChainDefinitionMap.put("/admin/index", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/admin/config/**", "anon");
filterChainDefinitionMap.put("/admin/doLogout", "logout");
;//后台登出
// 单独放入member的配置中 filterChainDefinitionMap.put("/member/doLogout","logout");//前端客户登出
filterChainDefinitionMap.put("/admin/**", "url"); //路径/shiro_admin/**全部需要进行权限验证
//其他资源都需要认证 authc 表示需要认证才能进行访问 user表示配置记住我或认证通过可以访问的地址
filterChainDefinitionMap.put("/admin/**", "user");
shiroFilterFactoryBean.setFilters(customisedFilter);
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
// public
public URLPathMatchingFilter getURLPathMatchingFilter() {
return new URLPathMatchingFilter();
}
/*安全管理器*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置自定义realm.
securityManager.setRealm(getMyRealm());
//配置记住我
securityManager.setRememberMeManager(rememberMeManager());
// SecurityUtils.setSecurityManager(securityManager);
return securityManager;
}
// @Bean
// public AdminRealm getAdminRealm(){
// AdminRealm adminRealm = new AdminRealm();
// adminRealm.setCredentialsMatcher(hashedCredentialsMatcher());
// return adminRealm;
// }
@Bean
public MyRealm getMyRealm(){
MyRealm myRealm = new MyRealm();
myRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myRealm;
}
// @Bean
// public MemberRealm getMemberRealm(){
// MemberRealm memberRealm = new MemberRealm();
// memberRealm.setCredentialsMatcher(hashedCredentialsMatcher());
// return memberRealm;
// }
// @Bean
// public DatabaseRealm getDatabaseRealm() {
// DatabaseRealm myShiroRealm = new DatabaseRealm();
// myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
// return myShiroRealm;
// }
// @Bean
// public MemberDatabaseRealm getMemberDatabaseRealm() {
// MemberDatabaseRealm memberShiroRealm = new MemberDatabaseRealm();
// memberShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
// return memberShiroRealm;
// }
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* 所以我们需要修改下doGetAuthenticationInfo中的代码;
* )
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
/**
* cookie对象,会话Cookie模板,默认为:JSESSIONID
*
* @return
*/
@Bean
public SimpleCookie rememberMeCookie() {
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
simpleCookie.setHttpOnly(true);
simpleCookie.setPath("/admin/");
simpleCookie.setMaxAge(60 * 60 * 24 * 30);//一个月cookie保留时间
return simpleCookie;
}
/**
* cookie管理对象,记住我功能,rememberMe管理器
*
* @return
*/
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
return cookieRememberMeManager;
}
/**
* FormAuthenticationFilter过滤器 过滤记住我
*
* @return
*/
@Bean
public FormAuthenticationFilter formAuthenticationFilter() {
FormAuthenticationFilter formAuthenticationFilter = new FormAuthenticationFilter();
formAuthenticationFilter.setRememberMeParam("rememberMe");
return formAuthenticationFilter;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
你期待的结果是什么?实际看到的错误信息又是什么?
member登录会根据member的realm, admin会根据admin的
并且路径拦截(loginUrl,successUrl...)也会根据不同的进行设置.