手把手教你写项目之中小学微课学习系统(六):用户登录-芝麻开门!

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

效果预览:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

06-用户登录-芝麻开门!

书接上回!咱们已经把前后端通信的“电话线”给接好了,axios这个“通信兵”也已就位,后端服务也敞开了“跨域”的大门。今天,咱们就要干一件激动人心的大事——打通登录流程!让前端的“芝麻”能真正打开后端的“大门”。

我们的目标是:

  1. 找到前端项目的登录页面(login.html)。
  2. 给“登录”按钮绑定一个点击事件。
  3. 在事件中,获取用户输入的账号和密码。
  4. 调用我们封装好的axios,把账号密码发送到后端的 /auth/login 接口。
  5. 接收后端返回的JWT Token,并把它存到浏览器的 localStorage 里。
  6. 登录成功后,跳转到系统主页。

第一步:找到“作案现场” - Login.html

v2 目录下,找到 Login.html (或者类似名字的登录文件)。用你的代码编辑器打开它。你会看到一堆HTML代码,没关系,我们只需要关注两个地方:

  1. 用户名输入框的 id
  2. 密码输入框的 id
  3. 登录按钮的 idclass

假设通过查看HTML代码,我们发现它们的id分别是 usernamepasswordlogin-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);
    }

现在,整个登录和认证的闭环就完成了!

  1. 用户在前端登录。
  2. 前端拿到Token存起来。
  3. 前端访问其他受保护接口时,需要在请求头里带上Token。
  4. 后端的JwtAuthenticationFilter会拦截请求,校验Token。
  5. 校验通过,请求才能到达真正的Controller。

下一期预告:
用户终于能登录进来了!但进来之后发现,系统里空空如也,啥课程也没有。这可不行!下一章,我们将开始构建核心的课程模块,包括课程的增删改查接口,以及课程分类的管理,让我们的知识殿堂“丰富”起来!

源码下载地址:
https://download.youkuaiyun.com/download/THMAIL/91753658

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

THMAIL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值