Spring Boot2 实战系列之登录注册(二) - 登录实现

前言

在前面的博文 Spring Boot2 实战系列之登录注册(一) - 注册实现 中实现了一个基本的注册功能,这次继续把登录功能加上,采用 spring security 对用户进行认证,采用 session 管理用户登录状态。

项目架构

项目结构图如下:

Spring Boot2 实战系列之登录注册(二) - 登录实现_第1张图片

pom 依赖如下:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0modelVersion>
	<parent>
		<groupId>org.springframework.bootgroupId>
		<artifactId>spring-boot-starter-parentartifactId>
		<version>2.2.5.RELEASEversion>
		<relativePath/> 
	parent>
	<groupId>top.yekonglegroupId>
	<artifactId>springboot-login-sampleartifactId>
	<version>0.0.1-SNAPSHOTversion>
	<name>spring-boot-starter-parentname>
	<description>Login sample for Spring Bootdescription>

	<properties>
		<java.version>1.8java.version>
		<passay.version>1.5.0passay.version>
		<guava.version>29.0-jreguava.version>
	properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-webartifactId>
		dependency>
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-securityartifactId>
		dependency>
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-data-jpaartifactId>
		dependency>
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-thymeleafartifactId>
		dependency>
		<dependency>
			<groupId>org.passaygroupId>
			<artifactId>passayartifactId>
			<version>${passay.version}version>
		dependency>
		<dependency>
			<groupId>com.google.guavagroupId>
			<artifactId>guavaartifactId>
			<version>${guava.version}version>
		dependency>
		<dependency>
			<groupId>com.h2databasegroupId>
			<artifactId>h2artifactId>
			<scope>runtimescope>
		dependency>

		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-devtoolsartifactId>
			<scope>runtimescope>
			<optional>trueoptional>
		dependency>
		<dependency>
			<groupId>org.projectlombokgroupId>
			<artifactId>lombokartifactId>
			<optional>trueoptional>
		dependency>
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-testartifactId>
			<scope>testscope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintagegroupId>
					<artifactId>junit-vintage-engineartifactId>
				exclusion>
			exclusions>
		dependency>
		<dependency>
			<groupId>org.springframework.securitygroupId>
			<artifactId>spring-security-testartifactId>
			<scope>testscope>
		dependency>
	dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.bootgroupId>
				<artifactId>spring-boot-maven-pluginartifactId>
			plugin>
		plugins>
	build>

project>

代码编写

这里主要写出改动或新增的类,其他的则和注册实现篇基本一致

用户角色
UserAuthority.java

package top.yekongle.login.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import lombok.Data;
import lombok.NoArgsConstructor;

/** 
* @Description: 
* @Author: Yekongle 
* @Date: 2020年5月14日
*/

@Entity
@Data
@NoArgsConstructor
public class UserAuthority {
	@Id
	@GeneratedValue
	private Long id;
	private String username;
	private String role;
}

用户角色操作接口
UserAuthorityRepository.java

package top.yekongle.login.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import top.yekongle.login.entity.UserAuthority;

/** 
* @Description: 
* @Author: Yekongle 
* @Date: 2020年5月14日
*/
public interface UserAuthorityRepository extends JpaRepository<UserAuthority, Long> {
	List<UserAuthority> findByUsername(String username);
}

注册方法,注册用户时默认指定一个 “ROLE_USER” 角色并保存
UserServiceImpl.java

package top.yekongle.login.service.impl;

import java.util.Arrays;

import javax.transaction.Transactional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;
import top.yekongle.login.dto.UserDTO;
import top.yekongle.login.entity.User;
import top.yekongle.login.entity.UserAuthority;
import top.yekongle.login.exception.UserAlreadyExistException;
import top.yekongle.login.repository.UserAuthorityRepository;
import top.yekongle.login.repository.UserRepository;
import top.yekongle.login.service.UserService;

/**
 * @Description:
 * @Author: Yekongle
 * @Date: 2020年5月5日
 */
@Slf4j
@Service
public class UserServiceImpl implements UserService {

	@Autowired
	private UserRepository userRepository;

	@Autowired
	private UserAuthorityRepository userAuthorityRepository;

	@Autowired
	private PasswordEncoder passwordEncoder;

	@Transactional
	@Override
	public User registerNewUserAccount(UserDTO userDTO) throws UserAlreadyExistException {
		if (emailExists(userDTO.getEmail())) {
			throw new UserAlreadyExistException("该邮箱已被注册:" + userDTO.getEmail());
		}
		log.info("UserDTO:" + userDTO.toString());
		User user = new User();
		user.setEmail(userDTO.getEmail());
		user.setPassword(passwordEncoder.encode(userDTO.getPassword()));
		userRepository.save(user);

		UserAuthority userAuthority = new UserAuthority();
		userAuthority.setUsername(userDTO.getEmail());
		userAuthority.setRole("ROLE_USER");
		userAuthorityRepository.save(userAuthority);

		return user;
	}

	private boolean emailExists(String email) {
		return userRepository.findByEmail(email) != null;
	}
}

web mvc 配置,这里主要指定直接返回的页面
WebMvcConfig.java

package top.yekongle.login.config;

import java.util.Locale;

import org.springframework.web.servlet.LocaleResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;

/** 
* @Description: web mvc 配置
* @Author: Yekongle 
* @Date: 2020年5月8日
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
	
	
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        /*
         * 设置对"/"的请求映射到login, 如果没有逻辑业务,
         * 则没有必要用控制器方法对请求进行映射
         * */
        registry.addViewController("/").setViewName("forward:/login");
        registry.addViewController("/registration.html");
        registry.addViewController("/successRegister.html");
        registry.addViewController("/home.html");
        registry.addViewController("/logout.html");
        registry.addViewController("/invalidSession.html");
    }
}

web 安全配置,主要包括自定义用户认证,登入登出配置,访问控制,session 管理等。
WebSecurityConfig.java

	package top.yekongle.login.config;

	import org.springframework.beans.factory.annotation.Autowired;
	import org.springframework.context.annotation.Bean;
	import org.springframework.context.annotation.Configuration;
	import org.springframework.http.HttpMethod;
	import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
	import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
	import org.springframework.security.config.annotation.web.builders.HttpSecurity;
	import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
	import org.springframework.security.core.session.SessionRegistry;
	import org.springframework.security.core.session.SessionRegistryImpl;
	import org.springframework.security.core.userdetails.UserDetailsService;
	import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
	import org.springframework.security.crypto.password.PasswordEncoder;
	import org.springframework.security.web.authentication.AuthenticationFailureHandler;
	import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

	import org.springframework.security.web.session.HttpSessionEventPublisher;
	import top.yekongle.login.security.MyLogoutSuccessHandler;
	import top.yekongle.login.security.MyUserDetailServiceImpl;

	/**
	* @Description: Web 安全配置
	* @Author: Yekongle
	* @Date: 2020年5月5日
	*/
	@Configuration
	public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

		@Autowired
		private MyUserDetailServiceImpl userDetailsService;

		@Autowired
		private AuthenticationSuccessHandler authenticationSuccessHandler;

		@Autowired
		private AuthenticationFailureHandler authenticationFailureHandler;

		@Autowired
		private MyLogoutSuccessHandler myLogoutSuccessHandler;

		// 使用 BCrypt强哈希方法来加密密码, 每次加密结果不一样
		@Bean
		public PasswordEncoder passwordEncoder() {
			return new BCryptPasswordEncoder();
		}


		/*
		* 可以有多个 AuthenticationProvider,默认使用 DaoAuthenticationProvide
		* DaoAuthenticationProvider 在进行认证的时候需要一个 UserDetailsService 来获取用户的信息 UserDetails
		* 其中包括用户名、密码和所拥有的权限
		* 当其中一个 AuthenticationProvider 认证成功后,后续 provider不再认证
		* */
		@Bean
		public DaoAuthenticationProvider authenticationProvider() {
			DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
			authenticationProvider.setUserDetailsService(userDetailsService);
			authenticationProvider.setPasswordEncoder(passwordEncoder());
			return authenticationProvider;
		}

		/* 注册 session 创建和销毁监听器,以便用于支持 session 并发控制
		 * 通知 Spring Security 更新会话注册表
		 * 实际上创建的监听只使用销毁事件
		 **/
		@Bean
		public HttpSessionEventPublisher httpSessionEventPublisher() {
			return new HttpSessionEventPublisher();
		}

		// 跟踪活跃的session
		@Bean
		public SessionRegistry sessionRegistry() {
			return new SessionRegistryImpl();
		}

		/*
		 * 用户认证
		 * 这里使用通用的用户认证,还有基于内存的用户和JDBC中的用户
		 * 数据访问方式可以是多种多样,包括非关系型数据库, 这时就需先自定义实现 UserDetailsService 接口来获取用户信息
		 * */
		@Override
		protected void configure(AuthenticationManagerBuilder auth) throws Exception {
			auth.authenticationProvider(authenticationProvider());
		}

		/*
		 * 请求授权配置
		 * */
		@Override
		protected void configure(HttpSecurity http) throws Exception {
			http
			.csrf().disable()
			.authorizeRequests()
				// 允许访问 H2 DB 控制台
				.antMatchers("/h2/**").permitAll()
				.antMatchers("/css/**", "/js/**", "/fonts/**").permitAll()
				.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
				.antMatchers("/user/registration*", "/registration*", "/successRegister*", "/login*", "/logout*").permitAll()
				.antMatchers("/invalidSession*").anonymous()
				.anyRequest().authenticated()
			.and()
				.formLogin().loginPage("/login")
				// 使用自定义登录成功处理
				.successHandler(authenticationSuccessHandler)
				// 使用自定义登录成功处理
				.failureHandler(authenticationFailureHandler)
				.permitAll()
			.and()
				   .sessionManagement()
				   // 无效 session 跳转
				   .invalidSessionUrl("/invalidSession.html")
				   // 确保单个用户的单个账号,只有一个活跃的session
				   .maximumSessions(1).sessionRegistry(sessionRegistry()).and()
				   // 创建一个新的HTTP会话后,使旧的HTTP会话无效,并将旧会话的属性复制过来
				   .sessionFixation().migrateSession()
			.and()
				.logout()
			    // 使用自定义注销登录成功处理
				.logoutSuccessHandler(myLogoutSuccessHandler)
				// 会清空所有已定义的session
				.invalidateHttpSession(false)
				// 删除 cookie
				.deleteCookies("JSESSIONID")
				.permitAll();
		}

	}

实现用户信息接口,自定义获取用户信息的方法,主要时实现了 loadUserByUsername 方法,并返回一个封装了用户账号,密码,权限等信息的 UserDetails 类型的实例 User。
MyUserDetailServiceImpl.java

package top.yekongle.login.security;

import java.util.ArrayList;
import java.util.List;

import javax.transaction.Transactional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;
import top.yekongle.login.repository.UserAuthorityRepository;
import top.yekongle.login.repository.UserRepository;
import top.yekongle.login.entity.User;
import top.yekongle.login.entity.UserAuthority;
/** 
* @Description: 
* @Author: Yekongle 
* @Date: 2020年5月5日
*/

@Slf4j
@Service("userDetailsService")
@Transactional
public class MyUserDetailServiceImpl implements UserDetailsService {

	@Autowired
	private UserRepository userRepository;
	
	@Autowired
	private UserAuthorityRepository userAuthorityRepository;
	
	@Override
	public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
		log.info("Email:" + email);
		User user = userRepository.findByEmail(email);

		if (user == null) {
            throw new UsernameNotFoundException("找不到该用户: "+ email);
        }
		boolean enabled = true;
        boolean accountNonExpired = true;
        boolean credentialsNonExpired = true;
        boolean accountNonLocked = true;
        
        return new org.springframework.security.core.userdetails
        		.User(user.getEmail(), user.getPassword(), enabled, accountNonExpired
        				, credentialsNonExpired, accountNonLocked,	getAuthorities(user.getEmail()));
	}
	
	private List<GrantedAuthority> getAuthorities (String username) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        List<UserAuthority> userAuthorityList = userAuthorityRepository.findByUsername(username);
        log.info("role size:" + userAuthorityList.size());
        for (UserAuthority userAuthority : userAuthorityList) {
            authorities.add(new SimpleGrantedAuthority(userAuthority.getRole()));
        }
        return authorities;
    }
}

自定义登录成功处理器,这里主要是设置会话有效期和指定重定向页面
CustomAuthenticationSuccessHandler.java

package top.yekongle.login.security;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/**
* @Author: Yekongle 
* @Date: 2020年5月13日
*/
@Slf4j
@Component("authenticationSuccessHandler")
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

	private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
	
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {
		log.info("onAuthenticationSuccess");
		redirectStrategy.sendRedirect(request, response, "/home.html");

		// 获取session,如果 session不存在,则返回null。
		final HttpSession session = request.getSession(false);
		if (session != null) {
			// session 有效期 30 min
			session.setMaxInactiveInterval(30*60);
			String username = this.getCurrentUsername(authentication);
			session.setAttribute("user", username);
		}
		// 清除 session 中的 AUTHENTICATION_EXCEPTION 属性
		clearAuthenticationAttributes(request);
	}	
	
	
	private String getCurrentUsername(Authentication authentication) {
		String username = null;
		if (authentication.getPrincipal() instanceof UserDetails) {
			username = ((UserDetails) authentication.getPrincipal()).getUsername();
		} else {
			username = authentication.getName();
		}
		return username; 
	}

    protected void clearAuthenticationAttributes(final HttpServletRequest request) {
        final HttpSession session = request.getSession(false);
        if (session == null) {
            return;
        }
        session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
    }
	
}

自定义登录失败处理器,控制跳转,返回错误信息
CustomAuthenticationFailureHandler.java

package top.yekongle.login.security;

import java.io.IOException;
import java.util.Locale;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.LocaleResolver;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component("authenticationFailureHandler")
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException exception) throws IOException, ServletException {
        log.info("onAuthenticationFailure");
    	setDefaultFailureUrl("/login?error=true");

        super.onAuthenticationFailure(request, response, exception);
		 
        request.getSession()
            .setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception.getMessage());
    }
}

注销登录成功处理
MyLogoutSuccessHandler.java

package top.yekongle.login.security;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

/**
* @Author: Yekongle 
* @Date: 2020年5月13日
*/

@Component("myLogoutSuccessHandler")
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        final HttpSession session = request.getSession();
        if (session != null) {
        	// 清理自定义 session 属性信息
            session.removeAttribute("user");
        }

        response.sendRedirect("/logout.html?logSucc=true");
    }

}

登录请求处理
LoginController.java

package top.yekongle.login.controller;

import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

/**
* @Author: Yekongle 
* @Date: 2020年5月5日
*/

@Controller
public class LoginController {
	
	@GetMapping("/login")
	public String login() {
		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
		// 如果已经登录则跳转到 home 页面
		if (auth instanceof AnonymousAuthenticationToken) {
			return "login";
		} else {
			return "home";
		}
	}
}

登录页面
loign.html

<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta content="text/html;charset=UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"/>

    <style type="text/css">
        .middle {
            float: none;
            display: inline-block;
            vertical-align: middle;
        }
    style>
head>
<body>
    <div th:if="${param.error != null}" class="alert alert-danger" th:utext="${session[SPRING_SECURITY_LAST_EXCEPTION]}">errordiv>
    <div class="container">
        <h2>登录h2>
        <br/>
        <form name='loginForm' action="login" method="POST" onsubmit="return validate();">
            <div class="row">
                <div class="form-group col-md-6 vertical-middle-sm">
                    <label for="email">邮箱label> 
                    <input type="email" class="form-control" name="username" aria-describedby="emailHelp">
                div>
            div>      
         
            <div class="row">
                <div class="form-group col-md-6">
                    <label for="password">密码label> 
                    <input type="password" class="form-control" id="password" name="password">  
                div>
            div>
            <button type="submit" class="btn btn-primary">登录button>
             <a class="btn btn-default" th:href="@{/registration.html}" >没有账号?a>
        form>
    div>

    <script th:src="@{/js/jquery-3.5.1.min.js}" type="text/javascript">script>
    <script th:src="@{/js/bootstrap.min.js}" type="text/javascript">script>
    <script th:inline="javascript">
	    function validate() {
		        if (document.loginForm.username.value == "" && document.loginForm.password.value == "") {
		            alert("账号密码不能为空!");
		            document.loginForm.username.focus();
		            return false;
		        }
		        if (document.loginForm.username.value == "") {
		            alert("账号不能为空!");
		            document.loginForm.username.focus();
		            return false;
		        }
		        if (document.loginForm.password.value == "") {
		            alert("密码不能为空!");
		            document.loginForm.password.focus();
		            return false;
		        }
		    }
    script>

body>
html>

主页
home.html

<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta content="text/html;charset=UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"/>
head>
<body>

	<nav class="navbar navbar-default">
	  <div class="container-fluid">
	    <div class="navbar-header">
	      <a class="navbar-brand" href="#">homea>
	    div>
	      <ul class="nav navbar-nav navbar-right">
	        <li><a style="color:blue;" th:text="${session[user]}" >yekonglea>li>
	        <li><a style="color:orange;" th:href="@{/logout}" >logouta>li>
	      ul>
	  div>
	nav>

    <script th:src="@{/js/jquery-3.5.1.min.js}" type="text/javascript">script>
    <script th:src="@{/js/bootstrap.min.js}" type="text/javascript">script>
    <script th:inline="javascript">

    script>

body>
html>

注销登录结果页面
logout.html

<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta content="text/html;charset=UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"/>
head>

<body>
    <div class="container">
        <h1 id="error" class="alert alert-danger" th:if="${session[SPRING_SECURITY_LAST_EXCEPTION]}" >退出登录失败h1>
                
        <h1 id="success" class="alert alert-info" th:if="${param.logSucc}" >退出登录成功h1>
        <br/><br/><br/>
        <a class="btn btn-primary" th:href="@{/login}" >登录a>
    div>
body>

html>

无效 session 页面
invalidSession.html

<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta content="text/html;charset=UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"/>
head>
<body>
    <div class="container">
	    <h1 class="alert alert-danger" >登录过期,请重新登录h1>
	    <a class="btn btn-primary" th:href="@{/login}" >重新登录a>
    div>
    
    <script th:src="@{/js/jquery-3.5.1.min.js}" type="text/javascript">script>
    <script th:src="@{/js/bootstrap.min.js}" type="text/javascript">script>
body>

html>

运行演示

启动项目

  1. 访问 http://localhost:8080,会自动跳到登录页面,先点击跳到注册页面

Spring Boot2 实战系列之登录注册(二) - 登录实现_第2张图片

  1. 注册账号,邮箱:[email protected] 密码:A123456!
    Spring Boot2 实战系列之登录注册(二) - 登录实现_第3张图片

  2. 注册成功,立即登录
    在这里插入图片描述

  3. 输入刚刚注册的邮箱和密码
    Spring Boot2 实战系列之登录注册(二) - 登录实现_第4张图片

  4. 登录成功,跳转到主页,右侧可以显示返回了用户的邮箱
    在这里插入图片描述

  5. 如果超过了设定的会话有效期 30 min 没有操作行为,则会过期
    在这里插入图片描述

  6. 点击 logout,注销成功
    Spring Boot2 实战系列之登录注册(二) - 登录实现_第5张图片

项目已上传至 Github: https://github.com/yekongle/springboot-code-samples/tree/master/springboot-login-sample , 希望对小伙伴们有帮助哦。

参考链接:

  • https://v4.bootcss.com/docs/getting-started/introduction/
  • https://github.com/Baeldung/spring-security-registration

你可能感兴趣的:(Spring,Boot)