受限于很多学校的中央认证服务(CAS)并未提供合理的接口,又或是需要复杂的申请才可以调用 CAS,导致皮肤站无法直接用学校的 CAS 账户登录,本教程将引导你通过 Eduroam 为皮肤站增加使用 CAS 账户登录的方式。
Eduroam(Education Roaming)是专为科研和教育机构开发的安全的环球跨域无线漫游认证服务,现已覆盖全球七十余个国家和地区的超过 6000 家科研机构和教育机构。加入 Eduroam 联盟的机构成员可使用本机构提供的合法账号,在全球已加入 Eduroam 联盟的机构内实现无线网络访问的无障碍漫游。国内共有 300 多所高校支持加入了 Eduroam。
你可以在 这里 查询到您的单位是否接入了 Eduroam。如果您的高校能搜索到名为 eduroam
的无线网络,说明您的单位接入了 Eduroam。
不同的学校对于 Eduroam 可能有不同许可政策,部分学校不允许学生账户使用,Eduroam 的账户密码也可能完全与 CAS 账户无关,具体还需要查询您学校信息中心对于 Eduroam 账户的说明。
总之,接下来的教程我们将以此账户作为案例去进行讲解。
虽然 Eduroam 的账户形式与电子邮箱类似,但实际上两者也是无关的。对于这个例子,学校的 Eduroam 域名是
sit.edu.cn
,但该学生的校园邮箱实际上是200000000@mail.sit.edu.cn
,与上方的 Eduroam 账户不同。后文会介绍处理这种情况的方法。
多数情况下,或许直接使用插件就能实现皮肤站 Eduroam 登录。
下载压缩包并上传到皮肤站:auth-eduroam-0.1.1.zip。作者a1375625918@SUES
“使用 Eduroam 登录” 0.1.0 版本存在严重的安全问题,请确认版本号并及时更新至 0.1.1。
上传该压缩包会同时安装“使用 Eduroam 登录”和“要求设置密码”两个插件。
通过“使用 Eduroam 登录”插件注册的用户默认密码为空,无法使用 Email 或角色名登录皮肤站,也无法使用外置登录。
启用“要求设置密码”插件来让密码为空的用户设置密码。
在 .env 文件中设置环境变量。
EDUROAM_HOST
: 添加在用户名后的 Eduroam 域名。
EDUROAM_STORE_HOST
: 替换 EDUROAM_HOST
并存储在数据库中的电子邮箱域名。
不设置任何环境变量以允许所有 Eduroam 成员。此时用户名会原样发送给验证服务器。
对于一个具体高校的皮肤站而言,配置通常不应该被留空。
建议通过 MUA 皮肤站或其他 Union 联合认证成员皮肤站验证外校学生,而非允许所有 Eduroam 用户在贵校的站点进行注册。
(不设置)
设置 EDUROAM_HOST=sit.edu.cn
后,用户名会被加上 @sit.edu.cn
后缀再发送给验证服务器。这样,只有指定域名的成员才能成功登录。
如果 Eduroam 域名与电子邮箱域名相同,或者不考虑电子邮件相关功能,就只需要配置这一个环境变量。
EDUROAM_HOST=sit.edu.cn
同时设置 EDUROAM_HOST=sit.edu.cn
和 EDUROAM_STORE_HOST=mail.sit.edu.cn
,对于用户名为 200000000
的情况,插件会使用 200000000@sit.edu.cn
向 Eduroam 服务器发起验证,但使用 200000000@mail.sit.edu.cn
在数据库中查找或注册用户。
在学校 Eduroam 域名与电子邮件不一样,且希望互相兼容时尤为有用。
EDUROAM_HOST=sit.edu.cn
EDUROAM_STORE_HOST=mail.sit.edu.cn
在皮肤站“插件管理”中选择“使用 Eduroam 登录”插件的设置按钮进入配置。确保插件已经成功读取到了环境变量中的值。然后就可以进行测试了。
如果以上插件不适用你的情况,可以尝试手动修改皮肤站代码。
在代码中,我们希望实现的功能是,用户输入学工号(200000000)和 CAS 账户密码(12345678),程序自动将他修改为,200000000@sit.edu.cn(这是我校的 Eduroam 账户) 并交由 Eduroam 验证。
如果验证通过,则查找皮肤站数据库是否存在 200000000@mail.sit.edu.cn 这个邮箱,如果存在,则登录该邮箱对应的账户;如果不存在,则使用这个邮箱注册,且将用户提供的 CAS 密码加密后存入数据库,并帮助用户登录
@mail.sit.edu.cn 我校的校园邮箱后缀,因为我校皮肤站原先采用的是邮箱验证,需要与原先的邮箱账户兼容
推荐直接下载 源代码 并直接将文件放置在对应路径。
为了实现这部分功能,我们需要修改的有以下内容,您需要有访问和修改网站根目录的权限:
/app/Http/Controllers
目录下,新建 LoginController.php
,该文件将会处理登录逻辑和注册逻辑,以及账户验证。/resources/views
目录下,新建 eduroamlogin.blade.php
,该文件将会处理用户填写的表单,同时也是直接显示给用户的页面。/routes/web.php
,该文件是路由文件,简单来说可以让用户通过 {您的域名}/auth/eduroam/login
访问您的 Eduroam 注册页。<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Http;
use App\Models\User;
use Auth;
use Cache;
use Illuminate\Contracts\Events\Dispatcher;
use App\Events;
use Illuminate\Support\Facades\Hash;
use Carbon\Carbon;
//这里就是我们创建的类,接下来所有以 public 开头的程序都需要写在这里的大括号内
class LoginController extends Controller
{
}
public function showLoginForm()
{
return view('eduroamlogin'); // 确保视图文件名与实际文件名匹配
}
public function handleLogin(Request $request)
{
$studentNumber = $request->input('student_number');//
$password = $request->input('password');
$emailForAuthentication = $studentNumber . '@sit.edu.cn';//需要将这里修改成你学校 Eduroam 账户的域名
$emailForDatabase = $studentNumber . '@mail.sit.edu.cn'; //修改成您学校校园邮箱账户的域名
// 发送 POST 请求进行远程身份验证
$response = Http::asForm()->post('https://eduroam.ustc.edu.cn/cgi-bin/eduroam-test.cgi', [
'login' => $emailForAuthentication,
'password' => $password
]);
// 根据远程服务的响应处理登录逻辑
if (strpos($response->body(), 'EAP Failure') !== false) {
// 认证失败:用户名或密码错误
return back()->withErrors(['login' => '用户名或密码错误,请重新输入。']);
} elseif (strpos($response->body(), 'illegal') !== false) {
// 认证失败:非法操作
return back()->withErrors(['login' => '登录失败,原因:非法操作,请联系管理员。']);
} elseif (strpos($response->body(), 'EAP authentication completed successfully') !== false) { // 确保这里的'EAP authentication completed successfully'是eduroam认证成功的确切标志
// 认证成功,检查用户是否存在
$user = User::where('email', $emailForDatabase)->first();
if (!$user) {
// 用户不存在,创建新用户
$user = new User();
$user->email = $emailForDatabase;
$user->score = 2000; //皮肤站默认积分
$user->avatar = 0;
$user->verified = 1;
$user->password = Hash::make($password);
$user->ip = $request->ip();
$user->permission = User::NORMAL;
$user->register_at = Carbon::now();
$user->last_sign_at = Carbon::now()->subDay();
$user->save();
Auth::login($user, true);
return redirect('/user')->with('status', '注册成功并已登录。');
}
// 用户存在,直接登录
Auth::login($user, true);
return redirect('/user')->with('status', '登录成功。');
} else {
// 未知错误
return back()->withErrors(['login' => '登录失败,未知错误,请稍后重试或联系管理员。']);
}
以下代码中所有涉及到 SIT-Minecraft 的部分,包括域名之类的都需要换成您自己的内容
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录 - 统一认证</title>
<!-- Bootstrap CSS -->
<link href="https://www.bootcdn.cn/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom CSS -->
<link href="/app/style.7eb5d06.css" rel="stylesheet" crossorigin="anonymous">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://unpkg.com/@fortawesome/fontawesome-free@5.15.4/css/all.min.css" crossorigin>
<!-- Favicon -->
<link rel="shortcut icon" href="https://skin.sitmc.club/app/3.ico">
<link rel="icon" type="image/png" href="https://skin.sitmc.club/app/3.ico" sizes="192x192">
<link rel="apple-touch-icon" href="https://skin.sitmc.club/app/3.ico" sizes="180x180">
</head>
<body class="hold-transition login-page">
<div class="login-box">
<div class="login-logo">
<a href="https://skin.sitmc.club">SIT-Minecraft</a>
</div>
<div class="card">
<div class="card-body login-card-body">
<p class="login-box-msg">请使用您的统一认证账户登录</p>
<!-- 显示简单的文字错误信息 -->
@if ($errors->any())
<p style="color: red; font-size: 14px; text-align: center;">
@foreach ($errors->all() as $error)
{{ $error }}
@endforeach
</p>
@endif
<form action="/auth/eduroam/login" method="post">
@csrf <!-- CSRF Token -->
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="学号" name="student_number" required>
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-user-graduate"></span>
</div>
</div>
</div>
<div class="input-group mb-3">
<input type="password" class="form-control" placeholder="OA 密码" name="password" required>
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-lock"></span>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<button type="submit" id="loginButton" class="btn btn-primary btn-block">登录</button>
</div>
</div>
</br>
<div class="mb-1">其它登陆方式</div>
<a href="https://skin.sitmc.club/auth/login" class="btn btn-block bg-light border-secondary">
<i class="fab fa-mua"></i>
使用校园邮箱登陆
</a>
</br>
<div class="callout callout-info">
如果您尚未注册,我们将会自动帮您注册,注册账户即表明你同意<a href='https://www.sitmc.club/privacy/' target='_blank'>《SIT-Minecraft 隐私政策》</a>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
var loginForm = document.querySelector('form');
loginForm.addEventListener('submit', function() {
var loginButton = document.getElementById('loginButton');
loginButton.disabled = true; // 禁用按钮
loginButton.innerText = '等待中...'; // 更改按钮文字
});
});
</script>
</body>
</html>
修改路由文件,在 auth
组中添加路由
Route::prefix('auth')->name('auth.')->group(function () {
Route::middleware('guest')->group(function () {
Route::get('login', 'AuthController@login')->name('login');
Route::post('login', 'AuthController@handleLogin');
//这里的两行是需要添加的内容
Route::get('/eduroam/login', [LoginController::class, 'showLoginForm'])->name('auth.eduroam.login');
Route::post('/eduroam/login', [LoginController::class, 'handleLogin'])->name('auth.eduroam.login.post');
恭喜您,这样的话,你现在应该可以通过
{您的域名}/auth/eduroam/login
,访问 Eduroam 登录页面了
/resources/views/auth/rows
该路径下存放的文件是处理皮肤站原登录和注册页面的。/login/form.twig
并添加以下代码/register/form.twig
并添加以下代码这样的话,您的站点原先的登录和注册页面就会添加 Eduroam 登录入口,记得修改域名哦
<div class="mb-1">其它注册方式</div>
<a href="https://skin.sitmc.club/auth/eduroam/login" class="btn btn-block bg-light border-secondary">
使用学校 OA 账户登录
</a>
接入 Eduroam 登录后,由于涉及到学校统一认证系统的账号密码,您需要向注册皮肤站的用户介绍皮肤站的隐私政策。皮肤站仅存储单向加密、不可恢复的统一认证系统账号的密码作为皮肤站账号默认密码。
要注意的是:皮肤站的密码仅默认设置为统一认证系统的密码,本身和统一认证系统是独立的账号,两边的密码修改不会同步。
本文作者:JianMoOvO@SIT
感谢:chinazyq@ECUST(提供了代码的最初版本和思路),ff98sha@SJTU(提供了代码编写过程中的指导),Gugle@XATU(提供了代码编写过程中的指导)
以及 ChatGPT 对本教程和各种代码编写过程中出现的弱智问题的解决方案提供的大力帮助。