12、密码加密

12.1、不指定具体加密方式,通过DelegatingPasswordEncoder,根据前缀自动选择

PasswordEncoder passwordEncoder =
    PasswordEncoderFactories.createDelegatingPasswordEncoder();

image.png

12.2、指定具体加密方式

// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

// Create an encoder with all the defaults
Argon2PasswordEncoder encoder = new Argon2PasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

// Create an encoder with all the defaults
Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

// Create an encoder with all the defaults
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

13、密码自动更新

image.png
实现UserDetailsPasswordService接口即可
image.png

@Component("userDetailsImpl")
public class UserDetailsImpl implements UserDetailsService, UserDetailsPasswordService {
    @Autowired
   private UserMapper userMapper;
    @Autowired
   private RoleMapper roleMapper;

    @Override
    public org.springframework.security.core.userdetails.UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.loadUserByUsername(username);
        if(user == null){
            throw new UsernameNotFoundException("用户名不存在");
        }
        List<Role> roles = roleMapper.getRoleByUserId(user.getUserId());
        user.setRoles(roles);
        return user;
    }

    @Override
    public UserDetails updatePassword(UserDetails user, String newPassword) {
        int state = userMapper.updatePassword(newPassword, user.getUsername());
        if (state == 1) {
            ((User) user).setPassword(newPassword);
        }
        return user;
    }
}

14、Remember-Me

14.1、传统web方式

14.1.1、login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
<form th:action="@{/doLogin}" method="post">
    <p>用户名:<label>
        <input name="uname" type="text"/>
    </label></p>
    <p>密码:<label>
        <input name="pwd" type="password"/>
    </label></p>
    <p>记住我:<label>
        <input name="remember-me" type="checkbox"/>
    </label></p>
    <p>
        <input type="submit">
    </p>
</form>

</body>
</html>

14.1.2、开启rememberMeServices

  • 基于内存实现
package com.wanqi.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;

import java.util.UUID;

@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer {
    @Bean
    public PasswordEncoder bcryptPasswordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }


    public UserDetailsService inMemoryUsers(PasswordEncoder encoder) {
        // The builder will ensure the passwords are encoded before saving in memory
        UserDetails user = User.withUsername("user")
                .password(encoder.encode("123"))
                .roles("USER")
                .build();
        UserDetails admin = User.withUsername("admin")
                .password(encoder.encode("123"))
                .roles("USER", "ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user, admin);
    }

    @Bean(name = "rememberMeServices")
    public PersistentTokenBasedRememberMeServices rememberMeServices(){
       return new PersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(), inMemoryUsers(bcryptPasswordEncoder()), new InMemoryTokenRepositoryImpl());
    }


    @Bean
    SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        return
                //开启权限验证
                httpSecurity
                        .authorizeRequests()
                        //permitAll直接放行,必须在anyRequest().authenticated()前面
                        .mvcMatchers("/toLogin").permitAll()
                        //anyRequest所有请求都需要认证
                        .anyRequest().authenticated()
                        .and()
                        //使用form表单验证
                        .formLogin(login ->
                                login
                                        //自定义登陆页面
                                        .loginPage("/toLogin")
                                        //自定义登陆页面后必须指定处理登陆请求的url
                                        .loginProcessingUrl("/doLogin")
                                        // 自定义接收用户名的参数名为uname
                                        .usernameParameter("uname")
                                        //自定义接收密码的参数名为pwd
                                        .passwordParameter("pwd")
                                        // 认证成功后跳转的页面(转发),必须使用POST请求
                                        //.successForwardUrl("/test")
                                        // 证成功后跳转的页面(重定向),必须使用GET请求
                                        //.defaultSuccessUrl("/test")
                                        //不会每次都跳转定义的页面,默认会记录认证拦截的请求,如果是拦截的受限资源会优先跳转到之前被拦截的请求。需要每次都跳转使defaultSuccessUrl("/test",true)
                                        .defaultSuccessUrl("/toLogout",true)
                                        //前后端分离时代自定义认证成功处理
//                                        .successHandler(new MyAuthenticationSuccessHandler())
                                        //前后端分离时代自定义认证失败处理
//                                        .failureHandler(new MyAuthenticationFailureHandler())
                                        .failureUrl("/404")
                        )
                        //注销
                        .logout(logout -> {
                            logout
                                    //指定默认注销url,默认请求方式GET
//                                    .logoutUrl("/logout")
                                    .logoutRequestMatcher(
                                            //自定义注销url
                                            new OrRequestMatcher(
                                                    new AntPathRequestMatcher("/aa", "GET"),
                                                    new AntPathRequestMatcher("/bb", "POST")))
                                    //注销成功后跳转页面
                                    .logoutSuccessUrl("/toLogin")
                                    //前后端分离时代自定义注销登录处理器
//                                    .logoutSuccessHandler(new MyLogoutSuccessHandler())
                                    //销毁session,默认为true
                                    .invalidateHttpSession(true)
                                    //清除认证信息,默认为true
                                    .clearAuthentication(true);
                        })
                        //指定UserDetailsService来切换认证信息不同的存储方式(数据源)
                        .userDetailsService(inMemoryUsers(bcryptPasswordEncoder()))
                        //开启rememberMe
                        .rememberMe()
                        //自定义rememberMe参数名
//                        .rememberMeParameter();
                        .rememberMeServices(rememberMeServices())
                        .and()
                        //禁止csrf跨站请求保护
                        .csrf().disable()
                        .build();
    }
}
  • 持久化,基于mysql实现
    @Bean
    public PersistentTokenRepository jdbcTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //自动创建表结构,首次启动设置为true
//        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }
    @Bean
    SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        return
                //开启权限验证
                httpSecurity
                        .authorizeRequests()
                        //permitAll直接放行,必须在anyRequest().authenticated()前面
                        .mvcMatchers("/toLogin").permitAll()
                        .mvcMatchers("/404").permitAll()
                        //anyRequest所有请求都需要认证
                        .anyRequest().authenticated()
                        .and()
                        //使用form表单验证
                        .formLogin(login ->
                                login
                                        //自定义登陆页面
                                        .loginPage("/toLogin")
                                        //自定义登陆页面后必须指定处理登陆请求的url
                                        .loginProcessingUrl("/doLogin")
                                        // 自定义接收用户名的参数名为uname
                                        .usernameParameter("uname")
                                        //自定义接收密码的参数名为pwd
                                        .passwordParameter("pwd")
                                        // 认证成功后跳转的页面(转发),必须使用POST请求
                                        //.successForwardUrl("/test")
                                        // 证成功后跳转的页面(重定向),必须使用GET请求
                                        //.defaultSuccessUrl("/test")
                                        //不会每次都跳转定义的页面,默认会记录认证拦截的请求,如果是拦截的受限资源会优先跳转到之前被拦截的请求。需要每次都跳转使defaultSuccessUrl("/test",true)
                                        .defaultSuccessUrl("/toLogout",true)
                                        //前后端分离时代自定义认证成功处理
//                                        .successHandler(new MyAuthenticationSuccessHandler())
                                        //前后端分离时代自定义认证失败处理
//                                        .failureHandler(new MyAuthenticationFailureHandler())
                                        .failureUrl("/404")
                        )
                        //注销
                        .logout(logout -> {
                            logout
                                    //指定默认注销url,默认请求方式GET
//                                    .logoutUrl("/logout")
                                    .logoutRequestMatcher(
                                            //自定义注销url
                                            new OrRequestMatcher(
                                                    new AntPathRequestMatcher("/aa", "GET"),
                                                    new AntPathRequestMatcher("/bb", "POST")))
                                    //注销成功后跳转页面
                                    .logoutSuccessUrl("/toLogin")
                                    //前后端分离时代自定义注销登录处理器
//                                    .logoutSuccessHandler(new MyLogoutSuccessHandler())
                                    //销毁session,默认为true
                                    .invalidateHttpSession(true)
                                    //清除认证信息,默认为true
                                    .clearAuthentication(true);
                        })
                        //指定UserDetailsService来切换认证信息不同的存储方式(数据源)
                        .userDetailsService(inMemoryUsers(bcryptPasswordEncoder()))
                        //开启rememberMe
                        .rememberMe()
                        .tokenRepository(jdbcTokenRepository())
                        .and()
                        //禁止csrf跨站请求保护
                        .csrf().disable()
                        .build();
    }
}

14.2、前后端分离

14.2.1、自定义RememberMeServices实现类

package com.wanqi.service;

import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.servlet.http.HttpServletRequest;

/**
 * @Description 自定义RememberMeServices实现类
 * @Version 1.0.0
 * @Date 2022/9/5
 * @Author wandaren
 */

public class RememberMeServices extends PersistentTokenBasedRememberMeServices {

    public RememberMeServices(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) {
        super(key, userDetailsService, tokenRepository);
    }

    @Override
    protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
        String paramValue = request.getAttribute(AbstractRememberMeServices.DEFAULT_PARAMETER).toString();
        return paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on")
                || paramValue.equalsIgnoreCase("yes") || paramValue.equals("1");
    }
}

14.2.2、自定义认证方式

package com.wanqi.security.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
import org.springframework.util.ObjectUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

/**
 * @Description 处理Json方式的登录请求
 * @Version 1.0.0
 * @Date 2022/8/21
 * @Author wandaren
 */
public class JsonLoginFilter extends UsernamePasswordAuthenticationFilter {

    public JsonLoginFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        if (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) {
            try {
                Map<String, String> map = new ObjectMapper().readValue(request.getInputStream(), Map.class);
                String username = map.get(getUsernameParameter());
                username = (username != null) ? username.trim() : "";
                String password = map.get(getPasswordParameter());
                password = (password != null) ? password : "";
                String rememberMe = map.get(AbstractRememberMeServices.DEFAULT_PARAMETER);
                if (!ObjectUtils.isEmpty(rememberMe)) {
                    request.setAttribute(AbstractRememberMeServices.DEFAULT_PARAMETER, rememberMe);
                }
                UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
                        password);
                System.out.println(username);
                System.out.println(password);
                setDetails(request, authRequest);
                return getAuthenticationManager().authenticate(authRequest);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        throw new AuthenticationServiceException("Authentication ContentType not supported: " + request.getContentType());
    }
}

14.2.3、配置

filter.setRememberMeServices(rememberMeServices());

.rememberMe()
.rememberMeServices(rememberMeServices())
.tokenRepository(jdbcTokenRepository())
package com.wanqi.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wanqi.security.filter.JsonLoginFilter;
import com.wanqi.service.RememberMeServices;
import com.wanqi.service.impl.UserDetailsImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2022/9/5
 * @Author wandaren
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    DataSource dataSource;

    UserDetailsImpl userDetailsImpl;

    @Autowired
    public void setUserDetailsImpl(UserDetailsImpl userDetailsImpl) {
        this.userDetailsImpl = userDetailsImpl;
    }

    @Bean
    public PasswordEncoder bcryptPasswordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Autowired
    public AuthenticationConfiguration authenticationConfiguration;

    /**
     * 获取AuthenticationManager(认证管理器),登录时认证使用
     *
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public JsonLoginFilter jsonLoginFilter() throws Exception {
        JsonLoginFilter filter = new JsonLoginFilter(authenticationManager());
        //指定认证url
        filter.setFilterProcessesUrl("/doLogin");
        //指定接收json,用户名key
        filter.setUsernameParameter("uname");
        //指定接收json,密码key
        filter.setPasswordParameter("pwd");
        filter.setAuthenticationManager(authenticationManager());
        filter.setRememberMeServices(rememberMeServices());
        //前后端分离时代自定义认证成功处理
        filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                Map<String, Object> map = new HashMap<>();
                map.put("msg", "登陆成功");
                map.put("code", HttpStatus.OK.value());
                map.put("authentication", authentication);
                String s = new ObjectMapper().writeValueAsString(map);
                response.setContentType("application/json;charset=UTF-8");
                response.getWriter().write(s);
            }
        });
        //前后端分离时代自定义认证失败处理
        filter.setAuthenticationFailureHandler((request, response, exception) -> {
            Map<String, Object> map = new HashMap<>();
            map.put("msg", exception.getMessage());
            map.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
            String s = new ObjectMapper().writeValueAsString(map);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(s);
        });
        return filter;
    }

    @Bean
    public PersistentTokenRepository jdbcTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //自动创建表结构,首次启动设置为true
//        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }

    @Bean
    public RememberMeServices rememberMeServices(){
        return new RememberMeServices(UUID.randomUUID().toString(), userDetailsImpl, jdbcTokenRepository());
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .addFilterAt(jsonLoginFilter(), UsernamePasswordAuthenticationFilter.class)
                .logout(logout -> {
                    logout
                            .logoutRequestMatcher(
                                    //自定义注销url
                                    new OrRequestMatcher(
                                            new AntPathRequestMatcher("/aa", "GET"),
                                            new AntPathRequestMatcher("/bb", "POST")))
                            //前后端分离时代自定义注销登录处理器
                            .logoutSuccessHandler(new LogoutSuccessHandler() {
                                @Override
                                public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                                    Map<String, Object> map = new HashMap<>();
                                    map.put("msg", "注销成功");
                                    map.put("code", HttpStatus.OK.value());
                                    map.put("authentication", authentication);
                                    String s = new ObjectMapper().writeValueAsString(map);
                                    response.setContentType("application/json;charset=UTF-8");
                                    response.getWriter().write(s);
                                }
                            })
                            //销毁session,默认为true
                            .invalidateHttpSession(true)
                            //清除认证信息,默认为true
                            .clearAuthentication(true);
                })
                //指定UserDetailsService来切换认证信息不同的存储方式(数据源)
                .userDetailsService(userDetailsImpl)
                .exceptionHandling()
                .authenticationEntryPoint((request, response, authException) -> {
//                            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                    response.setHeader("content-type", "application/json;charset=UTF-8");
                    response.setStatus(HttpStatus.UNAUTHORIZED.value());
                    response.getWriter().write("请先认证后重试!");
                })
                .and()
                .rememberMe()
                .rememberMeServices(rememberMeServices())
                .tokenRepository(jdbcTokenRepository())
                .and()
                //禁止csrf跨站请求保护
                .csrf().disable()
                .build();
    }

}

15、会话管理

image.png

15.1、配置

package com.wanqi.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.session.HttpSessionEventPublisher;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2022/9/5
 * @Author wandaren
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception{
         httpSecurity.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .defaultSuccessUrl("/hello",true)
                .and()
                .csrf().disable()
                .sessionManagement(session -> session
                        //最多一个会话
                        .maximumSessions(1)
                        //true:超过会话上限不再容许登录
//                        .maxSessionsPreventsLogin(true)
                        // 会话失效(用户被挤下线后)跳转地址
                        // .expiredUrl("/login")
                    //  前后端分离处理方式
                        .expiredSessionStrategy(new SessionInformationExpiredStrategy() {
                            @Override
                            public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
                                event.getResponse().setHeader("content-type", "application/json;charset=UTF-8");
                                event.getResponse().setStatus(HttpStatus.UNAUTHORIZED.value());
                                event.getResponse().getWriter().write("会话超时!");
                            }
                        })
                );

         return httpSecurity.build();
    }

    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
}

15.2、共享会话

15.2.1、引入依赖

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.session</groupId>
			<artifactId>spring-session-data-redis</artifactId>
		</dependency>

15.2.2、编写配置

  • application.yml
spring:
  redis:
    host: 172.16.156.139
    port: 6379
    password: qifeng
    database: 0
  main:
    allow-circular-references: true
  • RedisSessionConfig
package com.wanqi.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.security.web.session.HttpSessionEventPublisher;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2022/9/5
 * @Author wandaren
 */
@Configuration
@EnableRedisHttpSession
public class RedisSessionConfig {
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.database}")
    private int database;

    @Bean
    RedisStandaloneConfiguration redisStandaloneConfiguration() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setPassword(password);
        redisStandaloneConfiguration.setDatabase(database);
        return redisStandaloneConfiguration;
    }

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(redisStandaloneConfiguration());
    }


    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
}
  • SecurityConfig
package com.wanqi.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.security.web.session.SessionInformationExpiredEvent;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.security.SpringSessionBackedSessionRegistry;

import javax.servlet.ServletException;
import java.io.IOException;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2022/9/5
 * @Author wandaren
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig<S extends Session> {
    @Autowired
    private FindByIndexNameSessionRepository<S> sessionRepository;

    @Bean
    public PasswordEncoder bcryptPasswordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public UserDetailsService inMemoryUsers(PasswordEncoder encoder) {
        // The builder will ensure the passwords are encoded before saving in memory
        UserDetails user = User.withUsername("user")
                .password(encoder.encode("123"))
                .roles("USER")
                .build();
        UserDetails admin = User.withUsername("admin")
                .password(encoder.encode("123"))
                .roles("USER", "ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user, admin);
    }

    @Bean
    SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception{
         httpSecurity.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .defaultSuccessUrl("/hello",true)
                .and()
                .csrf().disable()
                .sessionManagement(session -> session
                        //最多一个会话
                        .maximumSessions(1)
                        //true超过会话上限不再容许登录
                        .maxSessionsPreventsLogin(true)
//                        被踢下线后跳转地址
//                        .expiredUrl("/login")
                        .expiredSessionStrategy(new SessionInformationExpiredStrategy() {
                            @Override
                            public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
                                event.getResponse().setHeader("content-type", "application/json;charset=UTF-8");
                                event.getResponse().setStatus(HttpStatus.UNAUTHORIZED.value());
                                event.getResponse().getWriter().write("会话超时!");
                            }
                        })
                        .sessionRegistry(sessionRegistry())
                )
                .userDetailsService(inMemoryUsers(bcryptPasswordEncoder()))
         ;

         return httpSecurity.build();
    }

    @Bean
    public SpringSessionBackedSessionRegistry<S> sessionRegistry() {
        return new SpringSessionBackedSessionRegistry<>(this.sessionRepository);
    }

}

16、CSRF防御

16.1、传统web开发

  • 登录页面加上_csrf
 <input type="hidden" name="_csrf" th:value="${_csrf.getToken()}">
  • SecurityConfig配置修改
httpSecurity.csrf()

16.2、前后端分离开发

16.2.1、登陆页面不需要令牌

httpSecurity.csrf()
    //SpringSecurity处理登陆的默认方法不加令牌
    .ignoringAntMatchers("/doLogin")
    //将令牌保存到cookie,容许前端获取
    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
  • 登陆后返回cookie中包含XSRF-TOKEN

image.png

  • 后续请求在请求头增加X-XSRF-TOKEN,值为登陆cookie中的XSRF-TOKEN值

image.png

17、跨越处理

17.1、spring跨越处理

17.1.1、使用@CrossOrigin,可以作用到类上也可以作用到具体方法上

@RestController
public class HelloController {

    @RequestMapping("/hello")
    @CrossOrigin
    public String hello(){
        return "hello";
    }
}

17.1.2、实现WebMvcConfigurer接口重写addCorsMappings方法

package com.wanqi.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2022/9/6
 * @Author wandaren
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //对哪些请求进行跨域处理
        registry.addMapping("/**")
        .allowCredentials(false)
        .allowedHeaders("*")
        .allowedMethods("*")
        .allowedOrigins("*")
        .exposedHeaders("")
        .maxAge(3600)
        ;
    }
}

17.1.3、使用CorsFilter

    @Bean
    FilterRegistrationBean<CorsFilter> corsFilter() {
        FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedOrigins(Collections.singletonList("*"));
        corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
        corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
        corsConfiguration.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        registrationBean.setFilter(new CorsFilter(source));
        //指定Filter执行顺序
        registrationBean.setOrder(-1);
        return registrationBean;
    }

17.2、SpringSecurity跨域处理

17.2.1、 Security配置

@Bean
public CorsConfigurationSource configurationSource() {
    CorsConfiguration corsConfiguration = new CorsConfiguration();
    corsConfiguration.setAllowedOrigins(Collections.singletonList("*"));
    corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
    corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
    corsConfiguration.setMaxAge(3600L);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", corsConfiguration);
    return source;
}


httpSecurity.cors()
    .configurationSource(configurationSource())

相关文章

SpringSecurity入门(一)

SpringSecurity入门(二)

SpringSecurity入门(四)

未完待续

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部