Shiro 多Realm 认证和授权
一本正经教学时间
新人博主,求点赞求关注
1、多Realm
先分别编写好多个Realm各种认证和授权的规则 这里是StaffRealm.java 和 StudentRealm.java
//StaffRealm
public class StaffRealm extends AuthorizingRealm {
@Autowired
StaffRoleServiceImpl staffRoleService;
@Autowired
RolePermissionServiceImpl permissionService;
@Autowired
StaffServiceImpl staffService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRole(Constants.StaffRole);
Subject subject = SecurityUtils.getSubject();
Staff staff = (Staff) subject.getPrincipal();
List<Role> roles = staffRoleService.queryRoleByStaffId(staff.getId());
for(Role role:roles){
info.addRole(role.getName());
List<String> permissions = permissionService.queryStringPermissionByRoleId(role.getId());
info.addStringPermissions(permissions);
}
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken userToken = (UsernamePasswordToken)token;
try{
int id = Integer.parseInt(userToken.getUsername());
Staff staff = staffService.getById(id);
if(staff==null){
return null;
}
return new SimpleAuthenticationInfo(staff,staff.getPassword(),"StaffRealm");
}catch (NumberFormatException e){
return null;
}
}
}
//StudentRealm
public class StudentRealm extends AuthorizingRealm {
@Autowired
StudentService studentService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRole(Constants.StudentRole);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken userToken = (UsernamePasswordToken)token;
try{
String id = userToken.getUsername();
Student student = studentService.getById(id);
if(student==null){
return null;
}
return new SimpleAuthenticationInfo(student,student.getPassword(),"StudentRealm");
}catch (NumberFormatException e){
return null;
}
}
}
将这两个Realm注入到ShiroConfig的SecurityManager中,
@Bean(name="SecurityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("MySessionManager")DefaultWebSessionManager sessionManager){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//================================================//
List<Realm> realms = new ArrayList<>();
realms.add(staffRealm());
realms.add(studentRealm());
securityManager.setRealms(realms);//设置多Realm
//================================================//
return securityManager;
}
@Bean
public StaffRealm staffRealm(){
return new StaffRealm();
}
@Bean
public StudentRealm studentRealm(){
return new StudentRealm();
}
2、编写一个增强的UsernamePasswordToken
主要是为了在登录使用token时,能区分以那种方式认证,该token也会在授权时被使用
public class ShiroUsernamePasswordToken extends UsernamePasswordToken {
/*
* 当前登录用户类型
*/
private String userType;
public String getUserType() {
return userType;
}
public void setUserType(String userType) {
this.userType = userType;
}
public ShiroUsernamePasswordToken(String username, String password, String userType) {
super(username, password);
this.userType = userType;
}
}
3、重写ModularRealmAuthorizer和ModularRealmAuthenticator
ModularRealmAuthenticator:模块认证器 区分不同的Realm进行不同需求的身份验证
ModularRealmAuthorizer:模块授权器 区分不同的Realm进行不同需求的角色权限授权
UserModularRealmAuthenticator.java
我们重写ModularRealmAuthenticator中的doAuthenticate,以达到区分不同Realm认证的目的
public class UserModularRealmAuthenticator extends ModularRealmAuthenticator {
private Logger logger = LoggerFactory.getLogger(UserModularRealmAuthenticator.class);
/**
* 根据用户类型判断使用哪个Realm
*/
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
throws AuthenticationException {
super.assertRealmsConfigured();
// 强制转换回自定义的CustomizedToken
ShiroUsernamePasswordToken token = (ShiroUsernamePasswordToken) authenticationToken;
// 登录类型
String userType = token.getUserType();
// 所有Realm
Collection<Realm> realms = getRealms();
// 登录类型对应的所有Realm
Collection<Realm> typeRealms = new ArrayList<>();
for (Realm realm : realms) {
//根据登录类型和Realm的名称进行匹配区分
if(realm.getName().contains(userType)){
typeRealms.add(realm);
}
}
// 判断是单Realm还是多Realm,有多个Realm就会使用所有配置的Realm。 只有一个的时候,就直接使用当前的Realm。
if (typeRealms.size() == 1) {
logger.info("doSingleRealmAuthentication() execute ");
return doSingleRealmAuthentication(typeRealms.iterator().next(), token);
} else {
logger.info("doMultiRealmAuthentication() execute ");
return doMultiRealmAuthentication(typeRealms, token);
}
}
}
UserModularRealmAuthorizer.java
我们重写ModularRealmAuthorizer中的isPermitted和hasRole方法,达到用不同的Realm进行不同的授权规则的目的
public class UserModularRealmAuthorizer extends ModularRealmAuthorizer {
@Override
public boolean isPermitted(PrincipalCollection principals, String permission) {
assertRealmsConfigured();
// 所有Realm
Collection<Realm> realms = getRealms();
HashMap<String, Realm> realmHashMap = new HashMap<>(realms.size());
for (Realm realm : realms) {
if (realm.getName().contains(Constants.UserType.STAFF.type)) {
realmHashMap.put("StaffRealm", realm);
} else if (realm.getName().contains(Constants.UserType.STUDENT.type)) {
realmHashMap.put("StudentRealm", realm);
}
}
Set<String> realmNames = principals.getRealmNames();
if (realmNames != null) {
String realmName = null;
Iterator it = realmNames.iterator();
while (it.hasNext()) {
realmName = ConvertUtils.convert(it.next());
if (realmName.contains(Constants.UserType.STAFF.type)) {
return ((StaffRealm) realmHashMap.get("StaffRealm")).isPermitted(principals, permission);
} else if (realmName.contains(Constants.UserType.STUDENT.type)) {
return ((StudentRealm) realmHashMap.get("StudentRealm")).isPermitted(principals, permission);
}
break;
}
}
return false;
}
@Override
public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
assertRealmsConfigured();
// 所有Realm
Collection<Realm> realms = getRealms();
HashMap<String, Realm> realmHashMap = new HashMap<>(realms.size());
for (Realm realm : realms) {
if (realm.getName().contains(Constants.UserType.STAFF.type)) {
realmHashMap.put("StaffRealm", realm);
} else if (realm.getName().contains(Constants.UserType.STUDENT.type)) {
realmHashMap.put("StudentRealm", realm);
}
}
Set<String> realmNames = principals.getRealmNames();
if (realmNames != null) {
String realmName = null;
Iterator it = realmNames.iterator();
while (it.hasNext()) {
realmName = ConvertUtils.convert(it.next());
if (realmName.contains(Constants.UserType.STAFF.type)) {
return ((StaffRealm) realmHashMap.get("StaffRealm")).hasRole(principals, roleIdentifier);
} else if (realmName.contains(Constants.UserType.STUDENT.type)) {
return ((StudentRealm) realmHashMap.get("StudentRealm")).hasRole(principals, roleIdentifier);
}
break;
}
}
return false;
}
}
4、注入重写的ModularRealmAuthorizer和ModularRealmAuthenticator
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
List<Realm> realms = new ArrayList<>();
realms.add(staffRealm());
realms.add(studentRealm());
securityManager.setAuthenticator(modularRealmAuthenticator());
securityManager.setAuthorizer(modularRealmAuthorizer());
securityManager.setRealms(realms);
return securityManager;
}
@Bean
public UserModularRealmAuthorizer modularRealmAuthorizer(){
UserModularRealmAuthorizer modularRealmAuthorizer = new UserModularRealmAuthorizer();
return modularRealmAuthorizer;
}
@Bean
public ModularRealmAuthenticator modularRealmAuthenticator() {
UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
return modularRealmAuthenticator;
}
@Bean
public StaffRealm staffRealm(){
return new StaffRealm();
}
@Bean
public StudentRealm studentRealm(){
return new StudentRealm();
}
5、Controller的使用
@PostMapping("/login")
public String login(String username, String password, Model model){
Subject subject = SecurityUtils.getSubject();
ShiroUsernamePasswordToken token = new ShiroUsernamePasswordToken(username,password,Constants.UserType.STUDENT.type);//设置Realm的type "StudentRealm"
try{
subject.login(token);
Student student = (Student)subject.getPrincipal();
Session session = subject.getSession();
session.setAttribute(Constants.StudentNameSession,student.getName());
return "redirect:/";
}catch (UnknownAccountException e){
model.addAttribute("msg","用户名或密码错误");
return "student/login";
}catch (IncorrectCredentialsException e){
model.addAttribute("msg","用户名或密码错误");
return "student/login";
}
}
罗里吧嗦踩坑时间
在网上找了资料 有使用到 https://blog.csdn.net/sinat_35626559/article/details/94553393
中的代码
他写的也挺详细的,但是但是但是有个小坑,还是感谢分享
按照他的步骤我运行的时候发现认证确实可以区分不同的Realm了,授权却是一直走的StaffRealm里的授权,然后Student强转Staff报错。
因为当时只重写了ModularRealmAuthorizer里的isPermitted方法打了断点看看啥情况
人家都不进这个方法里,那区分个毛
ps:主要是我用的注解是@RequiresRoles只检查Role 当时没反应过来
然后我就重写了hasRole方法
终于进方法了,但是但是但是又说我没有角色??? 我都傻了
打断点到hasRole里从return false出去的 卧槽?
检查代码之后发现principals.getRealmNames()只有一个""值,后面匹配相应的Realm的时候都配对不上就return出去了
那principals.getRealmNames()为什么只有""呢?预想应该是"StaffRealm"或者 "StudentRealm"嘛
然后开始了无尽的打断点 无尽的看源码时间
最后知道了这个principal里面有个什么info的东西,正是认证的时候传过去的
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken userToken = (UsernamePasswordToken)token;
try{
String id = userToken.getUsername();
Student student = studentService.getById(id);
if(student==null){
return null;
}
return new SimpleAuthenticationInfo(student,student.getPassword(),"");//就是这玩意
}catch (NumberFormatException e){
return null;
}
}
我后面的参数写的就是"",一看源码
public SimpleAuthenticationInfo(Object principal, Object credentials, String realmName) {
this.principals = new SimplePrincipalCollection(principal, realmName);
this.credentials = credentials;
}
realmName就是这么传进来的
行吧 我改还不行嘛 然后就有了1、多Realm里写的样子
踩坑完毕!!!好好学习!天天向上!