项目结构
添加依赖
<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-8
cache: false
suffix: .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", // 登录url
data: $("#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 {
@Autowired
private MyUserDetailsService myUserDetailsService;
@Autowired
private MyAuthenticationService myAuthenticationService;
/**
* http请求处理方法
*
* @param http
* @throws Exception
*/
@Override
protected 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();
}
@Autowired
DataSource dataSource;
/**
* 持久化token,负责token与数据库之间的相关操作
*
* @return
*/
@Bean
public PersistentTokenRepository getPersistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);//设置数据源
// 启动时创建一张表, 第一次启动的时候创建, 第二次启动的时候需要注释掉, 否则会报错
// tokenRepository.setCreateTableOnStartup(true);
return tokenRepository;
}
/**
* WebSecurity
*
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
//解决静态资源被拦截的问题
web.ignoring().antMatchers("/css/**", "/images/**", "/js/**", "/favicon.ico");
}
/**
* 身份验证管理器
*
* @param auth
* @throws Exception
*/
@Override
protected 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 {
@Autowired
ObjectMapper objectMapper;
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public 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));
}
@Override
public 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));
}
@Override
public 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 {
@Autowired
UserService userService;
/**
* 根据username查询用户实体
*
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(username);// 用户名没有找到
}
// 先声明一个权限集合, 因为构造方法里面不能传入null
Collection<? 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")
// @ResponseBody
public String getCurrentUser() {
UserDetails userDetails = (UserDetails)
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
System.out.println("abc");
return "abc";
}
/**
* 获取当前登录用户
*
* @return
*/
@RequestMapping("/loginUser1")
@ResponseBody
public UserDetails getCurrentUser1() {
UserDetails userDetails = (UserDetails)
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return userDetails;
}
/**
* 获取当前登录用户
*
* @return
*/
@RequestMapping("/loginUser2")
@ResponseBody
public UserDetails getCurrentUser(Authentication authentication) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return userDetails;
}
/**
* 获取当前登录用户
*
* @return
*/
@RequestMapping("/loginUser3")
@ResponseBody
public UserDetails getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {
return userDetails;
}
@Autowired
private UserService userService;
/**
* 根据用户ID查询用户
*
* @return
*/
@GetMapping("/{id}")
@ResponseBody
public User getById(@PathVariable Integer id) {
//获取认证信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 判断认证信息是否来源于RememberMe
if (RememberMeAuthenticationToken.class.isAssignableFrom(authentication.getClass())) {
throw new RememberMeAuthenticationException("认证信息来源于RememberMe,请重新登录");
}
User user = userService.getById(id);
return user;
}
}
启动项目验证
访问任意接口,都会被路由到登录页面login.ftl,只有登录成功后,才能正常访问其他接口。