SpringBoot 整合shiro实现多Realm的控制
一、场景
假设在我们的系统中 有两个角色 用户 和 管理员
用户有100W 管理员只有 2个
1、假设是让你设计这个表 你会怎么设计?
经典的五张表
存在一个问题,用户和管理员 都是用户? 难道你要设计到一张表
现在的管理员登陆:通过用户名查询 需要查询100W+条数据 工作量太大
如果是我们能够将这两个角色的数据设计到两张表里面
管理员(2个人) 和100W人的用户表
2、管理员的表和用户的表 假设我们都使用shiro来进行认证 ? 怎么办?
设计思路
就是在用户登陆时候 设置一个 登陆类型
重写 UserNameAndPasswordToken 在执行正式的登陆的时候?根据登陆的类型选择 我们要使用的Realm 最终去进行认证 那么这个功能就搞定了
3、存在两个问题
1:在哪里去做选择 执行哪一个realm?
ModularRealmAuthenticator.java类进行拓展(直接和realm打交道的)
2:我们的登陆类型如何来定
二、编写代码
1、导包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--导入shiro的功能包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
2、配置application.properies
#Thymeleaf的配置
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.cache=false
3、编写登陆页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
下面是User的登陆<br>
<form action="/userLogin" method="post">
用户名:<input type="text" name="userName"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="用户登陆">
</form>
<hr>
下面是Admin的登陆
<form action="/adminLogin" method="post">
用户名:<input type="text" name="userName"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="用户登陆">
</form>
</body>
</html>
4、编写user模块登陆成功的页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
this is user index page And you?
</body>
</html>
5、编写Admin模块登陆成功的页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
this is admin index page And you?
</body>
</html>
6、编写登陆类型的枚举
public enum LoginType {
USER("User"), ADMIN("Admin");
private String type; //定义的是登陆的类型
private LoginType(String type){
this.type=type;
}
@Override
public String toString() {
return this.type.toString();
}
}
7、编写校验器
public class CustomModularRealmAuthenticator extends ModularRealmAuthenticator {
/**
* 想干一件事
* 就是通过传入数据的类型 来选择使用哪一个Realm
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
//做Realm的一个校验
assertRealmsConfigured();
//获取前端传递过来的token
CustomToken customToken=(CustomToken)authenticationToken;
//现在就可以获取这个登陆的类型了
String loginType = customToken.getLoginType(); // 登陆类型 1:User Admin
//获取所有的realms()
Collection<Realm> realms = getRealms();
//登陆类型对应的所有realm全部获取到
Collection<Realm> typeRealms=new ArrayList<>();
for (Realm realm:realms){
//realm类型和现在登陆的类型做一个对比
if(realm.getName().contains(loginType)){ //就能分开这两个realm
typeRealms.add(realm);
}
}
if(typeRealms.size()==1){
return doSingleRealmAuthentication(typeRealms.iterator().next(),customToken);
}else{
return doMultiRealmAuthentication(typeRealms,customToken);
}
}
}
8、编写UserController
@Controller
public class UserController {
//用户登陆的类型
private static final String LOGIN_TYPE= LoginType.USER.toString();
private Logger logger= LoggerFactory.getLogger(UserController.class);
/**
* 用户登陆的方法
* @param user
* @return
*/
@RequestMapping("userLogin")
public String login(User user){
//封装请求对象
CustomToken customToken=new CustomToken(user.getUserName(),user.getPassword(),LOGIN_TYPE);
//获取登陆主体
Subject subject = SecurityUtils.getSubject();
try{
subject.login(customToken);
if(subject.isAuthenticated()){
//说明认证是成功的
return "user_index";
}
}catch (UnknownAccountException err){ //用户名不对
logger.error("用户名不对");
}catch (IncorrectCredentialsException err){//密码不对
logger.error("密码不对");
}catch (Exception err){ //其他错误 造成登陆失败
logger.error("其他错误造成了登陆错误");
}
return "login";
}
}
9、编写AdminController
@Controller
public class AdminController {
//用户登陆的类型
private static final String LOGIN_TYPE= LoginType.ADMIN.toString();
private Logger logger= LoggerFactory.getLogger(UserController.class);
/**
* 用户登陆的方法
* @param admin
* @return
*/
@RequestMapping("adminLogin")
public String login(Admin admin){
//封装请求对象
CustomToken customToken=new CustomToken(admin.getUserName(),admin.getPassword(),LOGIN_TYPE);
//获取登陆主体
Subject subject = SecurityUtils.getSubject();
try{
subject.login(customToken);
if(subject.isAuthenticated()){
//说明认证是成功的
return "admin_index";
}
}catch (UnknownAccountException err){ //用户名不对
logger.error("用户名不对");
}catch (IncorrectCredentialsException err){//密码不对
logger.error("密码不对");
}catch (Exception err){ //其他错误 造成登陆失败
logger.error("其他错误造成了登陆错误");
}
return "login";
}
}
10、编写User对象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = 1841757056845722315L;
private int id;
private String userName;
private String password;
}
11、编写Admin对象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Admin implements Serializable {
private int id;
private String userName;
private String password;
}
12、编写配置文件
@SpringBootConfiguration
public class ShiroConfig {
//拦截的过滤器的配置
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(
@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置下如果认证没有成功的登陆地址
shiroFilterFactoryBean.setLoginUrl("/toLogin");
Map<String,String> maps=new HashMap<>();
maps.put("/toLogin","anon");
maps.put("/userLogin","anon");
maps.put("/adminLogin","anon");
maps.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(maps);
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
//配置下SecurityManager
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置校验器
securityManager.setAuthenticator(authenticator());
List<Realm> realms=new ArrayList<>();
realms.add(userRealm());
realms.add(adminRealm());
//设置Realm
securityManager.setRealms(realms);
return securityManager;
}
@Bean
public UserRealm userRealm(){
UserRealm userRealm = new UserRealm();
return userRealm;
}
@Bean
public AdminRealm adminRealm(){
AdminRealm adminRealm = new AdminRealm();
return adminRealm;
}
//下面就是认证器的配置
@Bean
public CustomModularRealmAuthenticator authenticator(){
CustomModularRealmAuthenticator authenticator = new CustomModularRealmAuthenticator();
return authenticator;
}
}
13、编写用户的Realm
public class UserRealm extends AuthorizingRealm {
private Logger logger= LoggerFactory.getLogger(AdminRealm.class);
@Override
public String getName() {
return "UserRealm";
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
logger.info("UserRealm执行了....");
//固定的
//第一步:获取用户名
String userName = (String) authenticationToken.getPrincipal();
//第二步:查询数据库
//查询中....
//查询中....
if(!(userName.equals("xiaobobo"))){
return null;
}
//下面就是查询出来的对象
User user = new User(1, "xiaobobo", "123");
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), getName());
return authenticationInfo;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
14、编写Admin的Realm
public class AdminRealm extends AuthorizingRealm {
private Logger logger= LoggerFactory.getLogger(AdminRealm.class);
@Override
public String getName() {
return "AdminRealm";
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//固定的
//第一步:获取用户名
String userName = (String) authenticationToken.getPrincipal();
//第二步:查询数据库
//查询中....
//查询中....
if(!(userName.equals("xiaowangzi"))){
return null;
}
logger.info("AdminRealm执行了....");
//下面就是查询出来的对象
Admin admin = new Admin(1, "xiaowangzi", "123");
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(admin.getUserName(), admin.getPassword(), getName());
return authenticationInfo;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
15、token的拓展类的编写
public class CustomToken extends UsernamePasswordToken {
//定义登陆的类型是为了在后面的校验中 去选择使用哪一个realm
private String loginType;
public CustomToken(String userName,String password,String loginType){
super(userName,password);
this.loginType=loginType;
}
public void setLoginType(String loginType) {
this.loginType = loginType;
}
public String getLoginType() {
return loginType;
}
}
16、登陆页面跳转的Controller编写
@Controller
public class IndexController {
@RequestMapping("toLogin")
public String toLogin(){
return "login";
}
}
希望大家关注我一波,防止以后迷路,有需要的可以加我Q讨论互相学习java ,学习路线探讨,经验分享与java Q:2415773436