SpringSecurity入门(三)

news2024/12/27 10:52:32

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入门(四)

未完待续

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1807654.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】前缀树(字典树)汇总

基础 {“a”,“abc”,“bac”,“bbc”,“ca” }的字典树如下图&#xff1a; 最主用的应用&#xff1a;一&#xff0c;字符串编码。二&#xff0c;位运算。 字符串编码 相比利用哈希映射编码&#xff0c;优点如下&#xff1a; 依次查询长度为n的字符串s的前缀时间复杂度是O(…

Qt图标字体文件中提取字体保存为图片

本文借用别人写的一个IconHelper来做说明。 1. 加载一个字体文件 QScopedPointer<IconHelper> iconHelper(new IconHelper(":/fa-regular-400.ttf", "Font Awesome 6 Pro Regular"));构造函数 IconHelper::IconHelper(const QString &fontFile…

C#操作MySQL从入门到精通(14)——汇总数据

前言 我们有时候需要对数据库查询的值进行一些处理,比如求平均值等操作,本文就是详细讲解这些用法,本文测试使用的数据库数据如下: 1、求平均值 求所有student_age 列的平均值 string sql = string.Empty; if (radioButton_AVG.Checked) {sql = “select AVG( student_…

【C#线程设计】2:backgroundWorker

实现&#xff1a; &#xff08;1&#xff09;.控件&#xff1a;group Box&#xff0c;text Box&#xff0c;check Box&#xff0c;label&#xff0c;botton&#xff0c;richtextbox 控件拉取见&#xff1a;https://blog.csdn.net/m0_74749240/article/details/139409510?spm1…

【CS.OS】操作系统如何使用分页和分段技术管理内存

1000.5.CS.OS.1.3-基础-内存管理-操作系统如何使用分页和分段技术管理内存-Created: 2024-06-09.Sunday10:24 操作系统的内存管理是一个复杂而关键的功能&#xff0c;它确保了程序可以高效、安全地运行。虚拟内存管理是其中一个重要的概念&#xff0c;它通过分页和分段技术来实…

【深度学习】Transformer分类器,CICIDS2017,入侵检测,随机森林、RFE、全连接神经网络

文章目录 1 前言2 随机森林训练3 递归特征消除 RFE Recursive feature elimination4 DNN5 Transformer5.1. 输入嵌入层&#xff08;Input Embedding Layer&#xff09;5.2. 位置编码层&#xff08;Positional Encoding Layer&#xff09;5.3. Transformer编码器层&#xff08;T…

如何自我认同?是否需要执着于社会性认同?

一、自我认同与社会性认同 自我认同与社会性认同是两个相关但又有所区别的概念&#xff0c;它们分别反映了个体在内心深处对自身价值的认知&#xff0c;以及外界&#xff08;尤其是社会&#xff09;对个体价值的评价与接纳。 自我认同 自我认同是指个体基于自身的价值观、能…

【设计模式】创建型设计模式之 建造者模式

文章目录 一、介绍定义UML 类图 二、用法1 简化复杂对象具体构建过程省略抽象的 Builder 类省略 Director 类 三、用法2 控制对象构造方法、限制参数关系Guava 中使用建造者模式构建 cache 来进行参数校验 一、介绍 定义 建造者模式&#xff0c;将一个复杂的对象的构建过程与…

这两款kimi和豆包插件,用来辅助文献阅读和总结,太香了!娜姐亲测好用

我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 ChatGPT刚出来的时候&#xff0c;几款速读PDF的AI工具ChatDoc、ChatPDF也跟着火了起来&#xff0c;可见大家对于速读文献、总结文档需求很高。 我记得ChatPDF只有几次免费机会…

⌈ 传知代码 ⌋ 多模态COGMEN详解

&#x1f49b;前情提要&#x1f49b; 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间&#xff0c;对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…

深度解析地铁票务系统的技术架构与创新应用

在城市交通体系中&#xff0c;地铁作为一种快速、便捷的公共交通方式&#xff0c;已经成为现代都市生活的重要组成部分。而地铁票务系统的技术架构&#xff0c;则是支撑地铁运营的核心之一。本文将深度解析地铁票务系统的技术架构与创新应用&#xff0c;从系统设计、数据管理、…

Zabbix配置中文显示及乱码问题

页面配置为中文显示 在zabbix 5.0版本开始用户菜单更改为左侧栏显示&#xff0c;找到并点击 User Settings&#xff0c;Language 修改语言为 Chinese (zh_CN) 即可。 PS&#xff1a;一般在部署后初始配置时&#xff0c;未找到 Chinese (zh_CN) 这一项&#xff0c;修改如下&…

ThreadCache线程缓存

一.ThreadCache整体结构 1.基本结构 定长内存池利用一个自由链表管理释放回来的固定大小的内存obj。 ThreadCache需要支持申请和释放不同大小的内存块&#xff0c;因此需要多个自由链表来管理释放回来的内存块.即ThreadCache实际上一个哈希桶结构&#xff0c;每个桶中存放的都…

【JAVASE】详讲java案例(中)

这篇接着讲用java语言写程序&#xff0c;本篇文章要讲三道题&#xff1a; &#xff08;1&#xff09;数字加密 &#xff08;2&#xff09;数组拷贝 &#xff08;3&#xff09;打印乘法表 一&#xff1a;数字加密 需求&#xff1a;某系统的数字密码是一个四位数&#xff0c…

⌈ 传知代码 ⌋ 深度知识追踪

&#x1f49b;前情提要&#x1f49b; 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间&#xff0c;对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…

最新大屏幕互动系统PHP源码 附动态背景图和配乐素材 含搭建教程

简介&#xff1a; 最新大屏幕互动系统PHP源码 附动态背景图和配乐素材 含搭建教程 测试环境&#xff1a;NginxPHP7.0MySQL5.6 ![CYA]CPZMY8NK8YADA.png](https://img-blog.csdnimg.cn/img_convert/1e38b378e1aa6e834f56ec9a83df064c.png)

在 Ubuntu 中安装 Docker

在 Ubuntu 中安装 Docker 首先&#xff0c;更新你的 Ubuntu 系统。 1、更新 Ubuntu 打开终端&#xff0c;依次运行下列命令&#xff1a; $ sudo apt update $ sudo apt upgrade $ sudo apt full-upgrade 2、添加 Docker 库 首先&#xff0c;安装必要的证书并允许 apt 包…

快速测试 Mybatis 复杂SQL,无需启动 Spring

快速测试mybatis的sql 当我们写完sql后&#xff0c;我们需要测试下sql是否符合预期&#xff0c;在填入各种参数后能否正常工作&#xff0c;尤其是对于复杂的sql。 一般我们测试可能是如下的代码: 由于需要启动spring&#xff0c;当项目较大的时候启动速度很慢&#xff0c;有些…

JAVAEE值网络编程(2)_TCP流套接字及通信模型、TCP网络编程及代码实例

前言 在上一节内容中&#xff0c;我们介绍了什么是套接字&#xff0c;以及使用UDP数据报套接字网络编程&#xff0c; 最后我们还介绍了Java数据报套接字通信模型以及相关代码实例。在这一节我们将会介绍TCP流套接字编程。 一、流套接字及通信模型 1.1 TCP套接字 TCP&#xff0…

elasticsearch hanlp 插件安装操作

elasticsearch hanlp 插件安装操作 下载 hanlp 插件上传hanlp插件到elasticsearch服务器安装hanlp插件kibana测试 下载 hanlp 插件 这里大家根据自己对应的 elasticsearch 版本下载匹配版本的 hanlp 插件&#xff0c;由于 hanlp 及 elasticsearch 各个版本之间差别较大&#x…