SpringSecurity入门(三)

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

未完待续

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

13. ESP32-HTTPClient(Arduino)

使用ESP32 Arduino框架的HTTPClient库进行HTTP请求 在ESP32开发里&#xff0c;网络通信是挺重要的一部分&#xff0c;你可能需要从服务器拿数据啊&#xff0c;或者把传感器数据发到云端什么的。不过别担心&#xff0c;ESP32 Arduino框架给我们提供了HTTPClient库&#xff0c;让…

阻塞队列和线程池

一、什么是阻塞队列 1.1 什么是队列 队列是先进先出。 队列是一种特殊的线性表&#xff0c;特殊之处在于它只允许在表的前端&#xff08;front&#xff09;进行删除操作&#xff0c;而在表的后端&#xff08;rear&#xff09;进行插入操作&#xff0c;和栈一样&#xff0c;队…

前端自测 - 那些经典的bug

前言 我一直坚持的一个观点&#xff0c;就是不以bug数论成败&#xff0c;但是这个需要加一个前提&#xff0c;就是不能出现那些低级的bug&#xff0c;更不能反复的出现。 由此整理了一系列我认为比较经典常见的前端bug&#xff0c;都是在项目中多次遇到过的&#xff0c;用于前…

Golang Context详解

文章目录 基本介绍context源码剖析Context接口emptyCtxcancelCtxtimerCtxvalueCtx context使用案例协程取消超时控制数据共享 基本介绍 基本介绍 在Go 1.7版本中引入了上下文&#xff08;context&#xff09;包&#xff0c;用于在并发编程中管理请求范围的数据、控制生命周期、…

vue-router全部搞定(附源码)

源码下载链接&#xff08;先转存&#xff0c;后下载&#xff09;&#xff1a;https://pan.quark.cn/s/b0c6edd68c21 怎么用vue-cli搭建项目 我们固然可以用传统htmljs的方式来搭建vue项目&#xff0c;但是如果组件很多&#xff0c;就需要通过Vue.component的方式一个个去引入…

Python的else子句7个妙用,原来还能这样用,整挺好!

## 1、条件语句else基础 &#x1f504; 1.1 简单else的常规操作 在Python中&#xff0c;else子句通常跟在if或一系列if-elif之后&#xff0c;提供一个“否则”的情况处理路径。如果前面的所有条件都不满足 &#xff0c;程序就会执行这里的代码块。例如 &#xff0c;检查一个数…

【STM32】GPIO输出(江科大)

一、GPIO简介 1.GPIO&#xff1a;通用输入输出口 2.可配置为8种输入输出模式 3.引脚电平&#xff1a;0-3.3V&#xff08;输出最大3.3V&#xff09;&#xff0c;部分引脚可容忍5V&#xff08;输入&#xff0c;有FT&#xff09; 4.输出模式下&#xff0c;可控制端口输出高低电平…

详解FedProx:FedAvg的改进版 Federated optimization in heterogeneous networks

FedProx&#xff1a;2020 FedAvg的改进 论文&#xff1a;《Federated Optimization in Heterogeneous Networks》 引用量&#xff1a;4445 源码地址&#xff1a; 官方实现&#xff08;tensorflow&#xff09;https://github.com/litian96/FedProx 几个pytorch实现&#xff1a;…

十二、【源码】配置注解执行SQL

源码地址&#xff1a;https://github.com/mybatis/mybatis-3/ 仓库地址&#xff1a;https://gitcode.net/qq_42665745/mybatis/-/tree/12-annotation 配置注解执行SQL 简化一下流程&#xff0c;主要可以分为下面几步&#xff1a; 1.解析配置&#xff0c;写入配置项 2.执行…

问题排查: Goalng Defer 带来的性能损耗

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 引言问题背景结论 引言 性能优化之路道阻且长&#xff0c;因为脱敏规定&#xff0c;…

Nginx 精解:正则表达式、location 匹配与 rewrite 重写

一、常见的 Nginx 正则表达式 在 Nginx 配置中&#xff0c;正则表达式用于匹配和重写 URL 请求。以下是一些常见的 Nginx 正则表达式示例&#xff1a; 当涉及正则表达式时&#xff0c;理解各个特殊字符的含义是非常重要的。以下是每个特殊字符的例子&#xff1a; ^&#xff1…

讯飞星火大模型个人API账号免费使用申请教程

文章目录 1.登录讯飞星火大模型官网 https://www.xfyun.cn/ 2.下滑找到Spark Lite&#xff0c;点击立即调用 3.星火大模型需要和具体的应用绑定&#xff0c;我们需要先创建一个新应用 https://console.xfyun.cn/app/myapp&#xff0c;应用名称可以按照自己的意愿起。 4.填写应用…

打造智慧工厂核心:ARMxy工业PC与Linux系统

智能制造正以前所未有的速度重塑全球工业格局&#xff0c;而位于这场革命核心的&#xff0c;正是那些能够精准响应复杂生产需求、高效驱动自动化流程的先进设备。钡铼技术ARMxy工业计算机&#xff0c;以其独特的设计哲学与卓越的技术性能&#xff0c;正成为众多现代化生产线背后…

ViT:2 理解CLIP

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则提…

vuInhub靶场实战系列--Kioptrix Level #3

免责声明 本文档仅供学习和研究使用,请勿使用文中的技术源码用于非法用途,任何人造成的任何负面影响,与本人无关。 目录 免责声明前言一、环境配置1.1 靶场信息1.2 靶场配置 二、信息收集2.1 主机发现2.1.1 netdiscover2.1.2 arp-scan主机扫描 2.2 端口扫描2.3 指纹识别2.4 目…

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

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

④-2单细胞学习-cellchat单数据代码补充版(通讯网络)

目录 通讯网络系统分析 ①社会网络分析 1&#xff0c;计算每个细胞群的网络中心性指标 2&#xff0c;识别细胞的信号流模式 ②非负矩阵分解&#xff08;NMF&#xff09;识别细胞的通讯模式 1&#xff0c;信号输出细胞的模式识别 2&#xff0c;信号输入细胞的模式识别 信…

RocketMq源码解析六:消息存储

一、消息存储核心类 rocketmq消息存储的功能主要在store这个模块下。 核心类就是DefaultMessageStore。我们看下其属性 // 配置文件 private final MessageStoreConfig messageStoreConfig; // CommitLog 文件存储实现类 private final CommitLog commitLog; …

【研发日记】Matlab/Simulink软件优化(三)——利用NaNFlag为数据处理算法降阶

文章目录 前言 背景介绍 初始算法 优化算法 分析和应用 总结 前言 见《【研发日记】Matlab/Simulink软件优化(一)——动态内存负荷压缩》 见《【研发日记】Matlab/Simulink软件优化(二)——通信负载柔性均衡算法》 背景介绍 在一个嵌入式软件开发项目中&#xff0c;需要开…

FedAvg论文

论文&#xff1a;Communication-Efficient Learning of Deep Networks from Decentralized Data 原code Reproducing 通过阅读帖子进行的了解。 联邦平均算法就是最典型的平均算法之一。将每个客户端上的本地随机梯度下降和执行模型的平均服务器结合在一起。 联邦优化问题 数…