shrio与springboot的简单整合:
1、Spring Boot 与Shrio的简单整合,适合做单体项目
2、Shrio 的会话管理,可以用在前后端分离的项目
3、Shrio 使用redis做缓存
可以参考文章:Shrio与SpringBoot整合(一)_我是混IT圈的-CSDN博客
这里主要是Shiro配置多个Realm进行鉴权:
总共三个Reaml,分别是老师、家长、学生,其他老师是免密登录,家长和学生是账号密码登录。
Shrio配置的时候,这里没有配置密码加密,所以这里的账号密码都是明文。
配置多个Realm有两种情况:
1、用户的登录只能使用1个Realm进行鉴权。
2、用户的登录需要通过多个Realm进行鉴权。
shiro在多个realm同时生效时提供了三种策略: 1、org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy 至少一个成功的策略-默认使用 2、org.apache.shiro.authc.pam.AllSuccessfulStrategy 全部成功策略 3、org.apache.shiro.authc.pam.FirstSuccessFulStrategy 第一个成功策略
管理realm的类是:ModularRealmAuthenticator
实现的过程简单说下:
重写 UsernamePasswordToken,加一个 loginType 属性,subject.login() 的时候传入 loginType; 重写 ModularRealmAuthenticator 中的 doAuthenticate() 方法,根据传进来的 loginType 来指定使用哪个 Realm。
废话不多说,上代码:
1、pom.xml 文件就不写了,如果不知道,看参考文档
2、创建Realm
老师:TeacherRealm
package com.example.springbootshrio.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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 java.util.HashSet;
import java.util.Set;
/**
* 老师的realm
* 老师的登录为免密登录
*/
public class TeacherRealm extends AuthorizingRealm {
//这里可以注入其他的服务,去查询用户的密码、查询用户的权限等信息
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//用户名
String username = (String) SecurityUtils.getSubject().getPrincipal();
System.out.println("-------根据用户名,获取权限--------");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//权限code,要唯一的,之后的权限验证就是通过匹配它来做的
Set<String> stringPermissions = new HashSet<>();
stringPermissions.add("teacher:list");
stringPermissions.add("teacher:update");
info.setStringPermissions(stringPermissions);
//还可以通过角色来做权限验证
Set<String> roles = new HashSet<>();
roles.add("老师");
info.setRoles(roles);
return info;
}
/**
* 这里可以注入userService,为了方便演示,我就写死了帐号了密码
* private UserService userService;
* <p>
* 用户名和密码的验证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println(getName()+"-------身份认证方法--------");
//用户名,也就是openId
String userName = (String) authenticationToken.getPrincipal();
//通过数据库查询这个openId是否正确,如果正确,那么就可以验证了,因为免密登录这里的密码都是固定的,比如都是123456
//根据用户名从数据库获取密码
String password = "123456";
if (userName == null) {
throw new AccountException("用户名或密码不正确");
}
//由shiro来做密码校验 如果身份认证验证成功,返回一个AuthenticationInfo实现;
return new SimpleAuthenticationInfo(userName, //用户名
password, //密码
getName() //当前 realm 的名字
);
}
}
家长:ParentsRealm
package com.example.springbootshrio.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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 java.util.HashSet;
import java.util.Set;
/**
* 家长的realm
* 需要账号密码登录
*/
public class ParentsRealm extends AuthorizingRealm {
//这里可以注入其他的服务,去查询用户的密码、查询用户的权限等信息
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//用户名
String username = (String) SecurityUtils.getSubject().getPrincipal();
System.out.println("-------根据用户名,获取权限--------");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//权限code,要唯一的,之后的权限验证就是通过匹配它来做的
Set<String> stringPermissions = new HashSet<>();
stringPermissions.add("parents:list");
stringPermissions.add("parents:update");
info.setStringPermissions(stringPermissions);
//还可以通过角色来做权限验证
Set<String> roles = new HashSet<>();
roles.add("家长");
info.setRoles(roles);
return info;
}
/**
* 这里可以注入userService,为了方便演示,我就写死了帐号了密码
* private UserService userService;
* <p>
* 用户名和密码的验证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println(getName()+"-------身份认证方法--------");
//用户名
String userName = (String) authenticationToken.getPrincipal();
if (userName == null) {
throw new AccountException("用户名或密码不正确");
}
//根据用户名从数据库获取密码
String password = "123";
//由shiro来做密码校验 如果身份认证验证成功,返回一个AuthenticationInfo实现;
return new SimpleAuthenticationInfo(userName, //用户名
password, //密码
getName() //当前 realm 的名字
);
}
}
学生:StudentsRealm
package com.example.springbootshrio.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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 java.util.HashSet;
import java.util.Set;
/**
* 学生的realm
* 需要账号密码登录
*/
public class StudentsRealm extends AuthorizingRealm {
//这里可以注入其他的服务,去查询用户的密码、查询用户的权限等信息
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//用户名
String username = (String) SecurityUtils.getSubject().getPrincipal();
System.out.println("-------根据用户名,获取权限--------");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//权限code,要唯一的,之后的权限验证就是通过匹配它来做的
Set<String> stringPermissions = new HashSet<>();
stringPermissions.add("students:list");
stringPermissions.add("students:update");
info.setStringPermissions(stringPermissions);
//还可以通过角色来做权限验证
Set<String> roles = new HashSet<>();
roles.add("学生");
info.setRoles(roles);
return info;
}
/**
* 这里可以注入userService,为了方便演示,我就写死了帐号了密码
* private UserService userService;
* <p>
* 用户名和密码的验证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println(getName()+"-------身份认证方法--------");
//用户名
String userName = (String) authenticationToken.getPrincipal();
if (userName == null) {
throw new AccountException("用户名或密码不正确");
}
//根据用户名从数据库获取密码
String password = "123";
//由shiro来做密码校验 如果身份认证验证成功,返回一个AuthenticationInfo实现;
return new SimpleAuthenticationInfo(userName, //用户名
password, //密码
getName() //当前 realm 的名字
);
}
}
系统自带的Realm管理,主要针对多realm:UserModularRealmAuthenticator
package com.example.springbootshrio.shiro;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* 系统自带的Realm管理,主要针对多realm
* 用来判断当前登录应该使用某个或者多个realm做登录授权
*/
public class UserModularRealmAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("UserModularRealmAuthenticator:method doAuthenticate() execute ");
// 判断getRealms()是否返回为空
assertRealmsConfigured();
// 强制转换回自定义的 CustomizedToken
UserToken userToken = (UserToken) authenticationToken;
// 登录类型
String loginType = userToken.getLoginType();
// 所有Realm
Collection<Realm> realms = getRealms();
// 登录类型对应的所有Realm
List<Realm> typeRealms = new ArrayList<>();
for (Realm realm : realms) {
if (realm.getName().contains(loginType)) {
typeRealms.add(realm);
}
}
// 判断是单Realm还是多Realm
if (typeRealms.size() == 1){
System.out.println("doSingleRealmAuthentication() execute ");
return doSingleRealmAuthentication(typeRealms.get(0), userToken);
} else{
System.out.println("doMultiRealmAuthentication() execute ");
return doMultiRealmAuthentication(typeRealms, userToken);
}
}
}
重写UsernamePasswordToken类,用来区分登录账号的时候,到底该使用那个Realm来做鉴权
package com.example.springbootshrio.shiro;
import org.apache.shiro.authc.UsernamePasswordToken;
public class UserToken extends UsernamePasswordToken {
//类型:Teacher-老师 Parents-家长 Students-学生
private String loginType;
public UserToken() {
}
public UserToken(final String username, final String password, final String loginType) {
super(username, password);
this.loginType = loginType;
}
public String getLoginType() {
return loginType;
}
public void setLoginType(String loginType) {
this.loginType = loginType;
}
}
Shiro的配置:ShiroConfig
需要配置shiroFilter,因为写了三个Realm进行鉴权,所以拦截器这里要把这三个都配置成免登录的。
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(org.apache.shiro.mgt.SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login.html");//登录页面
shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");//未经授权就可以访问的页面
shiroFilterFactoryBean.setSuccessUrl("/successUrl");//成功页面
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// anon:所有url都都可以匿名访问,一般写静态资源和登录页面等
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/login2", "anon");
filterChainDefinitionMap.put("/teacherLogin", "anon");//老师登录接口
filterChainDefinitionMap.put("/parentsLogin", "anon");//家长登录接口
filterChainDefinitionMap.put("/studentsLogin", "anon");//学生登录接口
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/front/**", "anon");
filterChainDefinitionMap.put("/api/**", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
//authc:所有url都必须认证通过才可以访问
filterChainDefinitionMap.put("/admin/**", "authc");
filterChainDefinitionMap.put("/user/**", "authc");
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 创建 SecurityManager 并且绑定 Realm
* @return
*/
@Bean
public org.apache.shiro.mgt.SecurityManager securityManager() {
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
//使用自定义的realm管理器 这个是新增的
defaultSecurityManager.setAuthenticator(modularRealmAuthenticator());
// 配置多个Realm也可以配置一个
List<Realm> realms = new ArrayList<>();
realms.add(customRealm());
realms.add(teacherRealm());
realms.add(parentsRealm());
realms.add(studentsRealm());
defaultSecurityManager.setRealms(realms);
// 自定义session管理
defaultSecurityManager.setSessionManager(sessionManager());
// 自定义缓存实现 使用redis
defaultSecurityManager.setCacheManager(redisCacheManager());
return defaultSecurityManager;
}
//自定义的三个Realm
@Bean
public TeacherRealm teacherRealm(){
TeacherRealm teacherRealm = new TeacherRealm();
return teacherRealm;
}
@Bean
public ParentsRealm parentsRealm(){
ParentsRealm parentsRealm = new ParentsRealm();
return parentsRealm;
}
@Bean
public StudentsRealm studentsRealm(){
StudentsRealm studentsRealm = new StudentsRealm();
return studentsRealm;
}
/**
* 系统自带的Realm管理,主要针对多realm
* */
@Bean
public ModularRealmAuthenticator modularRealmAuthenticator(){
//自己重写的ModularRealmAuthenticator
UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
/*
shiro在多个realm同时生效时提供了三种策略:
1、org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy 至少一个成功的策略-默认使用
2、org.apache.shiro.authc.pam.AllSuccessfulStrategy 全部成功策略
3、org.apache.shiro.authc.pam.FirstSuccessFulStrategy 第一个成功策略
可以通过 setAuthenticationStrategy 方法设置策略
这里就是如果登录类型同时存在多个realm时,应该怎么通过鉴权
*/
modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
return modularRealmAuthenticator;
}
使用:LoginController
package com.example.springbootshrio.controller;
import com.example.springbootshrio.shiro.UserToken;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class LoginController {
/**
* 老师免密登录
* 这里的免密登录一般用于微信小程序,当获取到openId时,密码都写死,比如都是123456,那么每次登录验证的时候,只需要验证openId是否正确就好了,因为密码都是123456而已。
*/
@ResponseBody
@PostMapping(value = "/teacherLogin")
public String teacherLogin(@RequestParam("username") String openId){
// 从 SecurityUtils 里边创建一个 subject
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备 token(令牌)
UserToken token = new UserToken(openId, "123456","Teacher");
//session会话
Session session = subject.getSession();
// 执行认证登陆
try {
subject.login(token);
} catch (UnknownAccountException uae) {
return "未知账户";
} catch (IncorrectCredentialsException ice) {
return "密码不正确";
} catch (LockedAccountException lae) {
return "账户已锁定";
} catch (ExcessiveAttemptsException eae) {
return "用户名或密码错误次数过多";
} catch (AuthenticationException ae) {
return "用户名或密码不正确!";
}
//登录成功,跳转到index页面
if (subject.isAuthenticated()) {
return session.getId().toString();
} else {
token.clear();
return "登录失败";
}
}
/**
* 家长用户名密码登录
*/
@ResponseBody
@PostMapping(value = "/parentsLogin")
public String parentsLogin(@RequestParam("username") String username, @RequestParam("password") String password){
// 从 SecurityUtils 里边创建一个 subject
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备 token(令牌)
UserToken token = new UserToken(username, password,"Parents");
//session会话
Session session = subject.getSession();
// 执行认证登陆
try {
subject.login(token);
} catch (UnknownAccountException uae) {
return "未知账户";
} catch (IncorrectCredentialsException ice) {
return "密码不正确";
} catch (LockedAccountException lae) {
return "账户已锁定";
} catch (ExcessiveAttemptsException eae) {
return "用户名或密码错误次数过多";
} catch (AuthenticationException ae) {
return "用户名或密码不正确!";
}
//登录成功,跳转到index页面
if (subject.isAuthenticated()) {
return session.getId().toString();
} else {
token.clear();
return "登录失败";
}
}
/**
* 学生用户名密码登录
*/
@ResponseBody
@PostMapping(value = "/studentsLogin")
public String studentsLogin(@RequestParam("username") String username, @RequestParam("password") String password){
// 从 SecurityUtils 里边创建一个 subject
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备 token(令牌)
UserToken token = new UserToken(username, password,"Students");
//session会话
Session session = subject.getSession();
// 执行认证登陆
try {
subject.login(token);
} catch (UnknownAccountException uae) {
return "未知账户";
} catch (IncorrectCredentialsException ice) {
return "密码不正确";
} catch (LockedAccountException lae) {
return "账户已锁定";
} catch (ExcessiveAttemptsException eae) {
return "用户名或密码错误次数过多";
} catch (AuthenticationException ae) {
return "用户名或密码不正确!";
}
//登录成功,跳转到index页面
if (subject.isAuthenticated()) {
return session.getId().toString();
} else {
token.clear();
return "登录失败";
}
}
}
html页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="/jquery.min.js"></script>
</head>
<body>
<span>老师登录</span>
<form method="post" action="/teacherLogin">
<table>
<tr>
<td>用户名:</td>
<td>密码:</td>
</tr>
<tr>
<td><input type="text" name="username" id="username1"></td>
<td><input type="text" name="password" id="password1"></td>
</tr>
<tr>
<td><input type="button" id="_button1" value="当前页面" /></td>
</tr>
</table>
</form>
<span>家长登录</span>
<form method="post" action="/parentsLogin">
<table>
<tr>
<td>用户名:</td>
<td>密码:</td>
</tr>
<tr>
<td><input type="text" name="username" id="username2"></td>
<td><input type="text" name="password" id="password2"></td>
</tr>
<tr>
<td><input type="button" id="_button2" value="当前页面" /></td>
</tr>
</table>
</form>
<span>学生登录</span>
<form method="post" action="/studentsLogin">
<table>
<tr>
<td>用户名:</td>
<td>密码:</td>
</tr>
<tr>
<td><input type="text" name="username" id="username3"></td>
<td><input type="text" name="password" id="password3"></td>
</tr>
<tr>
<td><input type="button" id="_button3" value="当前页面" /></td>
</tr>
</table>
</form>
</body>
<script>
var token = "";
//老师登录
$("#_button1").click(function () {
var username = $("#username1").val();
var password = $("#password1").val();
$.ajax({
type: "post",
url:"/teacherLogin",
data:{"username":username,"password":password},
success: function (res) {
alert(res);
token = res;
}
});
});
//家长登录
$("#_button2").click(function () {
var username = $("#username2").val();
var password = $("#password2").val();
$.ajax({
type: "post",
url:"/parentsLogin",
data:{"username":username,"password":password},
success: function (res) {
alert(res);
token = res;
}
});
});
//学生登录
$("#_button3").click(function () {
var username = $("#username3").val();
var password = $("#password3").val();
$.ajax({
type: "post",
url:"/studentsLogin",
data:{"username":username,"password":password},
success: function (res) {
alert(res);
token = res;
}
});
});
</script>
</html>
需要注意的是:
这里总共是需要重写两个类:
ModularRealmAuthenticator:shrio自带的多个Realm管理器,重写里面的doAuthenticate方法,来自定义让登录的账号执行其中某个或者多个Realm进行鉴权。
UsernamePasswordToken:登录时,存入账号密码的地方,在Realm鉴权时会从这里面取出来,但是这个类不能写入登录类型的字段,所以需要重写它,在里面加入登录类型字段,那么它就可以在?ModularRealmAuthenticator? 的重写方法里面根据登录类型的字段进行判,让登录用户使用其中某个或多个Realm进行鉴权了。
注意:登录类型的值要和创建的Realm的类名要一样,因为在?ModularRealmAuthenticator 的重写类中就是通过Realm的类名进行判断区分的。
代码如下:可以根据实际情况进行修改,代码都在??ModularRealmAuthenticator 的重写的类中。
// 所有Realm
Collection<Realm> realms = getRealms();
// 登录类型对应的所有Realm
List<Realm> typeRealms = new ArrayList<>();
for (Realm realm : realms) {
if (realm.getName().contains(loginType)) {
typeRealms.add(realm);
}
}
|