1. 项目目录
2. pom.xml
xml
复制代码
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.6.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.6.3</version>
</dependency>
3. 创建实体类
java
复制代码
package com.cnbai.entity;
/**
* 登录参数
*/
public class LoginForm {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
User
java
复制代码
package com.cnbai.entity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
/**
* 实现 SpringSecurity 提供的 UserDetails 接口,保存登录信息
*/
public class User implements UserDetails {
private String username;
private String password;
/** 用户的权限集,默认需要添加前缀 */
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
ArrayList<SimpleGrantedAuthority> list = new ArrayList<>();
list.add(new SimpleGrantedAuthority("USER"));
return list;
}
/** 用户的加密后的密码,不加密会使用前缀 */
@Override
public String getPassword() {
return password;
}
/** 应用内唯一的用户名 */
@Override
public String getUsername() {
return username;
}
/** 账户是否过期 */
@Override
public boolean isAccountNonExpired() {
return true;
}
/** 账户是否锁定 */
@Override
public boolean isAccountNonLocked() {
return true;
}
/** 凭证是否过期 */
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/** 用户是否可用 */
@Override
public boolean isEnabled() {
return true;
}
}
4. 创建拦截器
SessionInterceptor
java
复制代码
package com.cnbai.intercepter;
import com.cnbai.cache.RequestCache;
import com.cnbai.cache.SystemCache;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* 自定义拦截器,所有请求之前优先判断 token
*/
public class SessionInterceptor implements HandlerInterceptor {
/**
* 在处理请求之前被调用
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
if (token == null) {
return false;
}
HttpSession session = SystemCache.getSession(token);
if (session == null) {
return false;
}
RequestCache.setSession(session);
return true;
}
/**
* 在处理请求之后,页面视图渲染之前被调用
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
RequestCache.cleanSession();
}
/**
* 在处理请求完成且页面视图渲染完成后被调用
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("after...");
}
}
5. 创建配置类
WebConfig
java
复制代码
package com.cnbai.config;
import com.cnbai.intercepter.SessionInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 实现 spring 拦截器,自定义拦截范围
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SessionInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/login");
}
}
SystemConfig
java
复制代码
package com.cnbai.config;
import com.cnbai.cache.SystemCache;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* Spring容器启动之后,预加载资源,初始化 cache
*/
@Configuration
@Order(1)
public class SystemConfig implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
SystemCache.init();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
DiscoverSecurityConfig
java
复制代码
package com.cnbai.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource;
/**
* SpringSecurity 配置类
*/
@Configuration
@EnableWebSecurity
public class DiscoverSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserDetailsService userDetailsService;
@Resource
private PasswordEncoder passwordEncoder;
/** 身份认证 */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
/** 认证管理 */
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManager();
}
/** 核心过滤器 */
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/resources/**")
.antMatchers("/static/**");
}
/** 安全过滤器链 */
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests()
.antMatchers("/**").permitAll()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.accessDeniedPage("/404")
.and()
.csrf()
.disable();
}
}
6. 创建缓存
SystemCache
java
复制代码
package com.cnbai.cache;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
/**
* 枚举实现单例,维护全局缓存
* eq: 缓存 token 和 session 的关系
*/
public class SystemCache {
/** 初始化 cache */
public static void init() {
SessionCache.SESSION_CACHE.init();
}
/** 获取 session */
public static HttpSession getSession(String token) {
return SessionCache.SESSION_CACHE.getSession(token);
}
/** 缓存 token 和 session 的关系 */
public static void addSession(String token, HttpSession session) {
SessionCache.SESSION_CACHE.add(token, session);
}
/** 清空 session */
public static void clearSession(String token) {
SessionCache.SESSION_CACHE.clearSession(token);
}
//====================================================================================
private enum SessionCache {
SESSION_CACHE;
private final Map<String, HttpSession> cache;
/** 初始化 cache */
private void init() {
cache = new HashMap<>(1);
}
/** 缓存 token 和 session 的关系 */
private void add(String token, HttpSession session) {
this.cache.put(token, session);
}
/** 获取 session */
private HttpSession getSession(String token) {
return this.cache.get(token);
}
/** 清空 session */
private void clearSession(String token) {
this.cache.remove(token);
}
}
}
RequestCache
java
复制代码
package com.cnbai.cache;
import com.cnbai.entity.User;
import org.springframework.security.core.context.SecurityContextHolder;
import javax.servlet.http.HttpSession;
/**
* 用于存储鉴权用户,session 管理等,此类只维护每一次请求的缓存
*/
public class RequestCache {
private static final ThreadLocal<HttpSession> THREAD_LOCAL = new ThreadLocal<>();
private static final String CURRENT_USER = "user";
/** 添加 session 到 ThreadLocal */
public static void setSession(HttpSession httpSession) {
THREAD_LOCAL.set(httpSession);
}
/** 获取 session */
public static HttpSession getHttpSession() {
return THREAD_LOCAL.get();
}
/** 清空 session */
public static void cleanSession() {
THREAD_LOCAL.remove();
}
/** 添加 user 到 session */
public static void setUser(User user) {
getHttpSession().setAttribute(CURRENT_USER, user);
}
/** 获取 user */
public static User getUser() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof User) {
return (User) principal;
} else {
return (User) getHttpSession().getAttribute(CURRENT_USER);
}
}
}
7. 创建 Service
UserServiceImpl
java
复制代码
package com.cnbai.service;
import com.cnbai.cache.RequestCache;
import com.cnbai.cache.SystemCache;
import com.cnbai.entity.LoginForm;
import com.cnbai.entity.User;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
/**
* 用户接口
*/
public class UserServiceImpl {
@Resource
private AuthenticationManager authenticationManager;
/**
* 登录
*/
public User login(LoginForm loginForm, HttpServletRequest request) {
// 1. 通过 用户名 查询数据库用户信息
User userDetail = queryByName(loginForm.getUsername());
// 2. 验证密码
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetail, loginForm.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authenticate);
Object principal = authenticate.getPrincipal();
if (principal instanceof User) {
User user = (User) principal;
RequestCache.setSession(request.getSession());
RequestCache.setUser(user);
}
// 3. 缓存 token 和 session 的关系
String token = UUID.randomUUID().toString().replaceAll("-", "");
SystemCache.addSession(token, RequestCache.getHttpSession());
// 4. 返回结果
return RequestCache.getUser();
}
/**
* 注销
*/
public void logout(HttpServletRequest request) {
RequestCache.cleanSession();
SecurityContextHolder.clearContext();
SystemCache.clearSession(request.getHeader("token"));
}
}