Spring Boot 集成Shiro的多realm实现以及shiro基本入门教程

网友投稿 432 2022-11-17


Spring Boot 集成Shiro的多realm实现以及shiro基本入门教程

情景

我的项目中有六个用户角色(学校管理员,学生等),需要进行分别登陆。如果在一个realm中,对controller封装好的Token进行Service验证,需要在此realm中注入六个数据库操作对象,然后写一堆if语句来判断应该使用那个Service服务,然后再在验证方法(doGetAuthorizationInfo)中写一堆if来进行分别授权,这样写不仅会让代码可读性会非常低而且很难后期维护修改(刚写完的时候只有上帝和你能看懂你写的是什么,一个月之后你写的是什么就只有上帝能看懂了)。

所以一定要配置多个realm来分别进行认证授权操作。shiro有对多个realm的处理,当配置了多个Realm时,shiro会用自带的org.apache.shiro.authc.pam.ModularRealmAuthenticator类的doAuthenticate方法来进行realm判断,源码:

protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {

assertRealmsConfigured();

Collection realms = getRealms();

if (realms.size() == 1) {

return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);

} else {

return doMultiRealmAuthentication(realms, authenticationToken);

}

}

assertRealmsConfigured();的作用是验证realm列表是否为空,如果一个realm也没有则会抛出IllegalStateException异常(爆红:Configuration error: No realms have been configured! One or more realms must be present to execute an authentication attempt.)

当realm只有一个时直接返回,当realm有多个时返回所有的realm。而我们要做的就是写多个realm后重写ModularRealmAuthenticator下的doAuthenticate方法,使它能满足我们的项目需求。

那么改怎么重写ModularRealmAuthenticator下的doAuthenticate方法,使它能满足我们的项目需求呢?这就需要分析我们使用shiro的使用方法了。

shiro的使用

1.Controller层中,获取当前用户后将用户名和密码封装UsernamePasswordToken对象,然后调用Subject中的登陆方法subject.login(UsernamePasswordToken)

@RequestMapping("/user/login")

@ResponseBody

public String Login(String userName,String password){

//获取当前用户 subject

Subject subject = SecurityUtils.getSubject();

//封装用户的登陆数据

UsernamePasswordToken token =

new UsernamePasswordToken(userName, password);

try{

subject.login(token);//执行登陆方法

return "登陆成功";

}catch (UnknownAccountException e){//用户名不存在

model.addAttribute("msg","用户名不存在");

return "用户名不存在";

}catch (IncorrectCredentialsException e){//密码错误

model.addAttribute("msg","密码错误");

return "密码错误";

}

}

(为了测试方便,我用了@ResponseBody返回字符串)

2.完善自定义Realm类,继承于AuthorizingRealm,主要实现doGetAuthorizationInfo和doGetAuthenticationInfo方法

(需要实现认证和授权方法,在这里方便测试主要是认证)

public class StudentRealm extends AuthorizingRealm {

@Resource

private StudentsService studentsService;

//授权

@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

return null;

}

//认证

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

System.out.println("Shiro=========Student认证");

UserToken userToken = (UserToken) token;

Students students = studentsService.queryByNum(userToken.getUsername());

//账号不存在

if (students == null) {

System.out.println("学生不存在");

//向上层提交UnknownAccountException异常,在controller层处理

throw new UnknownAccountException();

}

//密码认证,shiro来做,可以自定义加密方式

return new SimpleAuthenticationInfo("", students.getPassword(), USER_LOGIN_TYPE);

}

}

3.配置shiro,将realm配置进shiro(很多教程是使用xml配置或者ini配置,在这里用java代码配置,功能都是一样的,看个人习惯了)

@Configuration

public class ShiroConfig {

@Bean

public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {

ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();

//设置安全管理器

bean.setSecurityManager(defaultWebSecurityManager);

return bean;

}

//DefaultWebSecurityManager 默认web安全管理器

@Bean(name = "securityManager")

public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {

DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

//关联realm

securityManager.setRealm(userRealm);

return securityManager;

}

//创建自定义 realm

@Bean

public UserRealm userRealm() {

return new UserRealm();

}

}

记得加@Configuration注解!!!!!!!

经过以上三步,可以看出shiro的简略工作流程(非常简略)就是,在web 启动阶段,读取

@Configuration注解将自定义的ream配置进默认web安全管理器(DefaultWebSecurityManager)然后将DefaultWebSecurityManager与ShiroFilterFactoryBean相关联。

当用户登陆时,从前端拿到username和password,封装好Token后,进入realm进行认证和授权,而realm就来自于刚才的shiro的DefaultWebSecurityManager配置

多realm实现原理

根据上面的shiro简略流程可知,shiro配置中写入多个realm后,在controller提交token时,只要多携带一个参数,用来进行org.apache.shiro.authc.pam.ModularRealmAuthenticator类的doAuthenticate(重写后)的验证即可明确应该用那个realm。那么,我们需要重写org.apache.shiro.authc.UsernamePasswordToken(令其携带身份参数用于选择realm)和org.apache.shiro.authc.pam.ModularRealmAuthenticator(令其根据token中的身份参数来进行选择realm)即可。

多realm实现具体操作

1.写多个自定义的realm

public class AdminRealm extends AuthorizingRealm {

@Resource

private AdminService adminService;

private static final String USER_LOGIN_TYPE = UserType.AdminRealm;

@Override

public String getName() {

return UserType.AdminRealm;

}

@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

return null;

}

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

System.out.println("Shiro=========Admin认证");

UserToken userToken = (UserToken) token;

Admin admin = adminService.queryById(userToken.getUsername());

if(admin == null){

System.out.println("管理员不存在");

throw new UnknownAccountException();

}

return new SimpleAuthenticationInfo("", admin.getAdminpassword(), USER_LOGIN_TYPE);

}

}

2.创建静态变量类(用于realm选择)

public class UserType {

//实习学校管理员

public static final String SchoolAdminRealm = "schooladminrealm";

//学生

public static final String StudentRealm ="studentrealm";

//管理员

public static final String AdminRealm ="adminrealm_1";

//导员

public static final String InstructorRealm ="instructorrealm";

//实习带队老师

public static final String UniversityteacherRealm ="universityteacherrealm";

//实习指导老师

public static final String SchoolTeacherRealm ="schoolteacherrealm";

}

3.重写UsernamePasswordToken,令其可以携带身份参数

@Component

public class UserModularRealmAuthenticator extends ModularRealmAuthenticator {

@Override

protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) {

// 判断getRealms()是否返回为空,ModularRealmAuthenticator 自带

assertRealmsConfigured();

// 强制转换回自定义的UserToken

UserToken token = (UserToken) authenticationToken;

String loginType = token.getLoginType();

Collection realms = getRealms();

for (Realm realm : realms) {

System.out.println(realm.getName().toLowerCase());

if (realm.getName().toLowerCase().contains(loginType)){

//找到登录类型对应的指定Realm

return doSingleRealmAuthentication(realm, token);

}

}

//没找到正确的realm的异常处理

String msg = "Configuration error: Didn't find the right realm";

throw new IllegalStateException(msg);

}

}

4.shiro的配置中写入自定义的realm,还有其它配置

@Configuration

public class ShiroConfig {

@Bean

public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityMaAYQGJvnager) {

ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();

//设置安全管理器

bean.setSecurityManager(defaultWebSecurityManager);

return bean;

}

//DefaultWebSecurityManager

@Bean(name = "securityManager")

public DefaultWebSecurityManager getDefaultWebSecurityManager(

@Qualifier("schoolAdminRealm") SchoolAdminRealm schoolAdminRealm,

@Qualifier("studentRealm") StudentRealm studentRealm,

@Qualifier("adminRealm") AdminRealm adminRealm,

@Qualifier("schoolTeacherRealm") SchoolTeacherRealm schoolTeacherRealm,

@Qualifier("instructorRealm") InstructorRealm instructorRealm,

@Qualifier("universityteacherRealm") UniversityteacherRealm universityteacherRealm,

@Qualifier("userModularRealmAuthenticator") UserModularRealmAuthenticator userModularRealmAuthenticator

) {

DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

securityManager.setAuthenticator(userModularRealmAuthenticator);

/**关联realm

*securityManager.setRealm() 是配置单个realm,不可用它配置多个realm

*securityManager.setRealms()配置多个realm,

*List realms可以直接被set进去

*/

List realms = new ArrayList();

realms.add(schoolAdminRealm);

realms.add(studentRealm);

realms.add(adminRealm);

realms.add(schoolTeacherRealm);

realms.add(instructorRealm);

realms.add(universityteacherRealm);

securityManager.setRealms(realms);

System.out.println(securityManager.getRealms().toString());

return securityManager;

}

//实习学校管理员

@Bean(name = "schoolAdminRealm")

public SchoolAdminRealm SchoolAdminRealm() {

return new SchoolAdminRealm();

}

//学生

@Bean(name = "studentRealm")

public StudentRealm StudentRealm() {

return new StudentRealm();

}

//管理员

@Bean(name = "adminRealm")

public AdminRealm AdminRealm() {

return new AdminRealm();

}

//导员

@Bean(name = "instructorRealm")

public InstructorRealm InstructorRealm() {

return new InstructorRealm();

}

//实习带队老师

@Bean(name = "universityteacherRealm")

public UniversityteacherRealm UniversityteacherRealm() {

return new UniversityteacherRealm();

}

//实习指导老师

@Bean(name = "schoolTeacherRealm")

public SchoolTeacherRealm SAYQGJvchoolTeacherRealm() {

return new SchoolTeacherRealm();

}

}

5.在controller中使用重写后的UsernamePasswordToken(UserToken)即可

//管理员登陆

@RequestMapping(value = "/AdminLogin", produces = "text/html;charset=UTF-8")

@ResponseBody//为了测试方便,返回字符串

public String AdminLogin(

@RequestParam(value = "username") String username,

@RequestParam(value = "password") String password) {

//获取当前用户 subject

Subject subject = SecurityUtils.getSubject();

//封装用户的登陆数据

UserToken token = new UserToken(username, Md5.getMd5(password), USER_LOGIN_TYPE);

try {

System.out.println("AdminLogin");

subject.login(token);//执行登陆方法

return null;

} catch (UnknownAccountException e) {//用户名不存在

System.out.println("用户名错误");

AYQGJv return null;

} catch (IncorrectCredentialsException e) {//密码错误

System.out.println("密码错误");

return null;

}

Spring Boot 集成Shiro的多realm配置


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:浅谈String.split()遇到空字符串的几种情况
下一篇:分模块构建Maven工程的方法步骤
相关文章

 发表评论

暂时没有评论,来抢沙发吧~