项目结构
添加依赖
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.9.RELEASE</version><relativePath/></parent><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId><version>2.4.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
修改配置文件
spring:freemarker:template-loader-path: classpath:/templates/charset: UTF-8cache: falsesuffix: .ftl
创建登录页
login.ftl
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>登录页</title>
</head>
<body>
<form id="formLogin" method="post">用户名<input type="text" name="username" value="admin"/>密码<input type="text" name="password" value="123456"/><input type="checkbox" name="remember-me" value="true"/>记住我<input type="button" onclick="login()" value="login"/>
</form>
</body>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script>function login() {$.ajax({type: "POST",//方法类型dataType: "json",//服务器预期返回类型url: "/login", // 登录urldata: $("#formLogin").serialize(),success: function (data) {console.log(data)if (data.code == 200) {window.location.href = "/";} else {alert(data.message);}}});}
</script>
</html>
创建首页
index.ftl
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>首页</title>
</head>
<body>
我是首页
</body>
</html>
创建SecurityConfiguration类
package com.lgg.config;import com.lgg.service.MyAuthenticationService;
import com.lgg.service.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.IOException;/*** Security配置类*/
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {@Autowiredprivate MyUserDetailsService myUserDetailsService;@Autowiredprivate MyAuthenticationService myAuthenticationService;/*** http请求处理方法** @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/toLoginPage", "/code/**").permitAll()//放行登录页面.anyRequest().authenticated()//所有请求都需要登录认证才能访问;.and().formLogin()//开启表单认证.loginPage("/toLoginPage")//自定义登录页面.loginProcessingUrl("/login")// 登录处理Url.usernameParameter("username").passwordParameter("password") //修改自定义表单name值..successHandler(myAuthenticationService)//自定义登录成功处理.failureHandler(myAuthenticationService)//自定义登录失败处理
// .defaultSuccessUrl("/")
// .successForwardUrl("/").and().logout().logoutUrl("/logout")//设置退出url.logoutSuccessHandler(myAuthenticationService)//自定义退出处理.and().rememberMe().rememberMeParameter("remember-me")// 自定义表单name值.tokenValiditySeconds(2 * 7 * 24 * 60 * 60)// 两周.tokenRepository(getPersistentTokenRepository())// 设置tokenRepository.and().csrf().disable(); // 关闭csrf防护// 允许iframe加载页面http.headers().frameOptions().sameOrigin();}@AutowiredDataSource dataSource;/*** 持久化token,负责token与数据库之间的相关操作** @return*/@Beanpublic PersistentTokenRepository getPersistentTokenRepository() {JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();tokenRepository.setDataSource(dataSource);//设置数据源// 启动时创建一张表, 第一次启动的时候创建, 第二次启动的时候需要注释掉, 否则会报错
// tokenRepository.setCreateTableOnStartup(true);return tokenRepository;}/*** WebSecurity** @param web* @throws Exception*/@Overridepublic void configure(WebSecurity web) throws Exception {//解决静态资源被拦截的问题web.ignoring().antMatchers("/css/**", "/images/**", "/js/**", "/favicon.ico");}/*** 身份验证管理器** @param auth* @throws Exception*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(myUserDetailsService);// 使用自定义用户认证}}
创建MyAuthenticationService类
package com.lgg.service;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;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.HashMap;
import java.util.Map;/*** 自定义登录成功或失败处理类*/
@Service
public class MyAuthenticationService implements AuthenticationSuccessHandler, AuthenticationFailureHandler, LogoutSuccessHandler {@AutowiredObjectMapper objectMapper;private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {System.out.println("登录成功后续处理....");
// redirectStrategy.sendRedirect(request, response, "/");
//
Map result = new HashMap();
result.put("code", HttpStatus.OK.value());// 设置响应码
result.put("message", "登录成功");// 设置响应信息
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(result));}@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {System.out.println("登录失败后续处理....");//redirectStrategy.sendRedirect(request, response, "/toLoginPage");
// Map result = new HashMap();
// result.put("code", HttpStatus.UNAUTHORIZED.value());// 设置响应码
// result.put("message", exception.getMessage());// 设置错误信息
// response.setContentType("application/json;charset=UTF-8");
// response.getWriter().write(objectMapper.writeValueAsString(result));}@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {System.out.println("退出成功后续处理....");redirectStrategy.sendRedirect(request, response, "/toLoginPage");}}
创建MyUserDetailsService类
package com.lgg.service;import com.lgg.entity.User;
import com.lgg.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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 java.util.ArrayList;
import java.util.Collection;/*** 基于数据库中完成认证*/
@Service
public class MyUserDetailsService implements UserDetailsService {@AutowiredUserService userService;/*** 根据username查询用户实体** @param username* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userService.findByUsername(username);if (user == null) {throw new UsernameNotFoundException(username);// 用户名没有找到}// 先声明一个权限集合, 因为构造方法里面不能传入nullCollection<? extends GrantedAuthority> authorities = new ArrayList<>();// 需要返回一个SpringSecurity的UserDetails对象UserDetails userDetails =new org.springframework.security.core.userdetails.User(user.getUsername(),
// "{noop}" + user.getPassword(),// {noop}表示不加密认证。"{bcrypt}" + user.getPassword(),true, // 用户是否启用 true 代表启用true,// 用户是否过期 true 代表未过期true,// 用户凭据是否过期 true 代表未过期true,// 用户是否锁定 true 代表未锁定authorities);return userDetails;}
}
创建HelloSecurityController类
package com.lgg.controller;import com.lgg.entity.User;
import com.lgg.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;/*** security入门案例*/
@Controller
public class HelloSecurityController {@RequestMapping("/toLoginPage")public String toLoginPage() {return "login";}/*** 获取当前登录用户** @return*/@RequestMapping("/abc")
// @ResponseBodypublic String getCurrentUser() {UserDetails userDetails = (UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();System.out.println("abc");return "abc";}/*** 获取当前登录用户** @return*/@RequestMapping("/loginUser1")@ResponseBodypublic UserDetails getCurrentUser1() {UserDetails userDetails = (UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();return userDetails;}/*** 获取当前登录用户** @return*/@RequestMapping("/loginUser2")@ResponseBodypublic UserDetails getCurrentUser(Authentication authentication) {UserDetails userDetails = (UserDetails) authentication.getPrincipal();return userDetails;}/*** 获取当前登录用户** @return*/@RequestMapping("/loginUser3")@ResponseBodypublic UserDetails getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {return userDetails;}@Autowiredprivate UserService userService;/*** 根据用户ID查询用户** @return*/@GetMapping("/{id}")@ResponseBodypublic User getById(@PathVariable Integer id) {//获取认证信息Authentication authentication = SecurityContextHolder.getContext().getAuthentication();// 判断认证信息是否来源于RememberMeif (RememberMeAuthenticationToken.class.isAssignableFrom(authentication.getClass())) {throw new RememberMeAuthenticationException("认证信息来源于RememberMe,请重新登录");}User user = userService.getById(id);return user;}}
启动项目验证
访问任意接口,都会被路由到登录页面login.ftl,只有登录成功后,才能正常访问其他接口。