SpringBoot 整合 SpringSecurity

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. 创建实体类

LoginForm

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"));
    }
}
相关推荐
xlsw_17 分钟前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹1 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭2 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫2 小时前
泛型(2)
java
超爱吃士力架2 小时前
邀请逻辑
java·linux·后端
南宫生2 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石2 小时前
12/21java基础
java
李小白662 小时前
Spring MVC(上)
java·spring·mvc
GoodStudyAndDayDayUp2 小时前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea
装不满的克莱因瓶3 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb