阅读前请先下载项目源码,边读边看源码以加深理解和实操,
源码地址已放于文章末尾!
效果预览:





06-用户登录-芝麻开门!
书接上回!咱们已经把前后端通信的“电话线”给接好了,axios这个“通信兵”也已就位,后端服务也敞开了“跨域”的大门。今天,咱们就要干一件激动人心的大事——打通登录流程!让前端的“芝麻”能真正打开后端的“大门”。
我们的目标是:
- 找到前端项目的登录页面(
login.html)。 - 给“登录”按钮绑定一个点击事件。
- 在事件中,获取用户输入的账号和密码。
- 调用我们封装好的
axios,把账号密码发送到后端的/auth/login接口。 - 接收后端返回的
JWT Token,并把它存到浏览器的localStorage里。 - 登录成功后,跳转到系统主页。
第一步:找到“作案现场” - Login.html
在 v2 目录下,找到 Login.html (或者类似名字的登录文件)。用你的代码编辑器打开它。你会看到一堆HTML代码,没关系,我们只需要关注两个地方:
- 用户名输入框的
id。 - 密码输入框的
id。 - 登录按钮的
id或class。
假设通过查看HTML代码,我们发现它们的id分别是 username、password 和 login-btn。
第二步:编写“开门咒语” - JavaScript代码
在 Login.html 的底部,</body> 标签之前,我们通常会找到或创建一个 <script> 标签,用来写我们的JavaScript逻辑。如果项目里已经有了 Login.js 这样的文件,那就更好了,我们直接在那个文件里修改。
为了代码清晰,我们新建一个 Scripts/Login.js 文件(如果已有就直接用),然后在 Login.html 中引入它:
<!-- ... other html ... -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="./Scripts/api.js"></script> <!-- 我们把axios配置单独放一个文件 -->
<script src="./Scripts/Login.js"></script>
</body>
</html>
首先,我们来创建 Scripts/api.js,专门存放 axios 的配置和全局API实例。这是一种好的编程习惯,叫做“关注点分离”。
Scripts/api.js:
const apiClient = axios.create({
baseURL: 'http://localhost:8080', // 我们的后端地址
timeout: 5000, // 请求超时时间
});
然后,就是我们今天的重头戏 Scripts/Login.js。
Scripts/Login.js:
// 等待整个页面的DOM都加载完毕后,再执行我们的代码
document.addEventListener('DOMContentLoaded', function() {
// 1. 获取DOM元素
const usernameInput = document.getElementById('username');
const passwordInput = document.getElementById('password');
const loginButton = document.getElementById('login-btn');
const errorMessageDiv = document.getElementById('error-message'); // 假设有一个显示错误信息的div
// 2. 给登录按钮绑定点击事件
if (loginButton) {
loginButton.addEventListener('click', function() {
// 3. 获取输入框的值
const username = usernameInput.value;
const password = passwordInput.value;
// 基本的非空校验
if (!username || !password) {
errorMessageDiv.innerText = '用户名和密码不能为空!';
return;
}
// 4. 调用axios发送登录请求
apiClient.post('/auth/login', {
username: username,
password: password
})
.then(response => {
// 5. 请求成功的回调
console.log('登录成功!', response.data);
// 从返回的数据中拿到token
const token = response.data.token;
// 如果成功获取到token
if (token) {
// 6. 把token存到localStorage
localStorage.setItem('jwt_token', token);
// 7. 跳转到主页
window.location.href = 'index.html'; // 假设主页是index.html
} else {
errorMessageDiv.innerText = '登录失败,请检查返回数据!';
}
})
.catch(error => {
// 请求失败的回调
console.error('登录失败:', error);
if (error.response && error.response.status === 401) {
errorMessageDiv.innerText = '用户名或密码错误!';
} else {
errorMessageDiv.innerText = '登录请求失败,请稍后再试。';
}
});
});
}
});
代码“翻译官”时间
document.addEventListener('DOMContentLoaded', ...):这是一个保险措施,确保在操作页面元素之前,这些元素已经被浏览器完全加载出来了。localStorage.setItem('jwt_token', token):这是关键一步!localStorage是浏览器提供的一个本地存储方案,像一个小仓库。我们把后端给的“通行证”(Token)存进去,这样在网站的其他页面,我们就能再把它取出来,证明我们是“已登录”状态。window.location.href = 'index.html':登录成功后,用这行代码实现页面的跳转。
第三步:完善后端的“安保流程”
光把前端的登录打通还不够。我们还需要完善上一章留下的一个“尾巴”——让后端能校验前端请求中携带的Token。这需要用到一个叫做“过滤器”(Filter)的东西。
我们要创建一个JwtAuthenticationFilter,它的作用就像是每个API接口前的“保安”。每个请求过来,它都会先检查一下请求头(Header)里有没有带合法的“通行证”(JWT Token)。
在 config 包下创建 JwtAuthenticationFilter.java:
package com.weke.learningsystem.config;
import com.weke.learningsystem.utils.JwtUtil;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
try {
String username = JwtUtil.validateToken(token);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// 注意:这里的UserDetails我们是“模拟”了一个,并没有从数据库查。
// 在复杂的应用中,你可能需要根据username从数据库加载真实的用户信息和权限。
UserDetails userDetails = new User(username, "", new ArrayList<>());
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
// Token验证失败
System.out.println("Token validation error: " + e.getMessage());
}
}
chain.doFilter(request, response);
}
}
最后,把这个“保安”安排上岗。回到 SecurityConfig.java,在 configure(HttpSecurity http) 方法的最后,加上我们的过滤器。
SecurityConfig.java:
// ... imports ...
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
// ... class definition ...
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 在UsernamePasswordAuthenticationFilter前添加我们的JWT过滤器
http.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
现在,整个登录和认证的闭环就完成了!
- 用户在前端登录。
- 前端拿到Token存起来。
- 前端访问其他受保护接口时,需要在请求头里带上Token。
- 后端的
JwtAuthenticationFilter会拦截请求,校验Token。 - 校验通过,请求才能到达真正的Controller。
下一期预告:
用户终于能登录进来了!但进来之后发现,系统里空空如也,啥课程也没有。这可不行!下一章,我们将开始构建核心的课程模块,包括课程的增删改查接口,以及课程分类的管理,让我们的知识殿堂“丰富”起来!
源码下载地址:
https://download.youkuaiyun.com/download/THMAIL/91753658

1704

被折叠的 条评论
为什么被折叠?



