• Spring Security 简单token配置


    Spring Security 简单token配置

    说明:非表单配置

    先上码: https://gitee.com/qkzztx_admin/security-demo/tree/master/demo-two

    环境:win10 idea2023 springboot2.7.6 maven3.8.6

    代码清单说明

    依赖:

    <dependency>
         <groupId>org.springframework.bootgroupId>
         <artifactId>spring-boot-starter-securityartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    Spring Security配置:

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.http.SessionCreationPolicy;
    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.bcrypt.BCryptPasswordEncoder;
    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.UsernamePasswordAuthenticationFilter;
    
    import javax.annotation.Resource;
    
    @Configuration
    @EnableWebSecurity
    @EnableMethodSecurity
    public class SecurityConfig {
        @Resource
        private MyAccessDeniedHandler myAccessDeniedHandler;
        @Resource
        private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
        @Resource
        private AuthFilter authFilter;
    
        /**
         * security配置
         */
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http.csrf().disable() // 禁用csrf
                    .authorizeRequests().antMatchers("/login").permitAll() // 允许任何人访问登录接口
                    .and()
                    .sessionManagement(sessionManager -> sessionManager.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 不再管理session
                    .authorizeRequests().anyRequest().authenticated() // 除了所有允许匿名访问的接口外,任何其他接口都得先认证
                    .and()
                    .exceptionHandling(handling -> {
                        handling.accessDeniedHandler(myAccessDeniedHandler) // 访问被拒绝的处理器
                                .authenticationEntryPoint(myAuthenticationEntryPoint); // 认证失败的处理入口
                    });
            // 设置用户访问前filter
            http.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class);
            return http.build();
        }
    
        /**
         * 用户数据
         */
        @Bean
        public InMemoryUserDetailsManager userDetailsService() {
            UserDetails user = User.builder()
                    .username("user")
                    .password(passwordEncoder().encode("password"))
                    .roles("USER")
                    .build();
            return new InMemoryUserDetailsManager(user);
        }
    
        /**
         * 密码加密类
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    放开/login的访问,方便用户认证,也就是抛弃了spring security的默认认证接口。
    配置中,还有简单的用户查询服务,和一个默认的密码加密Bean。

    import com.example.demo.two.controller.vo.R;
    import com.example.demo.two.controller.vo.UserRequest;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Objects;
    import java.util.UUID;
    
    @RestController
    public class LoginController {
        /**
         * 简单的存放用户登录认证成功信息的地方
         */
        public final static Map<String, UserDetails> TOKEN_USERNAME = new HashMap<>();
        /**
         * SecurityConfig中配置的userDetailsService
         */
        @Resource
        private UserDetailsService userDetailsService;
        /**
         * SecurityConfig中配置的密码加密
         */
        @Resource
        private PasswordEncoder passwordEncoder;
        /**
         * 登录认证,获得token
         * @param userRequest 用户名和密码
         * @return 认证token
         */
        @PostMapping("/login")
        public R login(@RequestBody UserRequest userRequest) {
            UserDetails userDetails = userDetailsService.loadUserByUsername(userRequest.getUsername());
            if (Objects.isNull(userDetails)) {
                return R.fail("用户名不存在");
            }
            if(passwordEncoder.matches(userRequest.getPassword(), userDetails.getPassword())) {
                // 认证成功发个token
                String token = UUID.randomUUID().toString();
                // 认证成功信息,简单存储
                TOKEN_USERNAME.put(token, userDetails);
                return R.success("登录成功", token);
            }
            return R.fail("密码不正确");
        }
    
        @GetMapping("/")
        public String index() {
            return "首页";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    简单的认证接口,注入了用户查询服务和密码加密Bean。
    查询用户,判断密码,生成token,保存token,返回token,很简单。
    还有一个首页接口是需要认证才能访问。

    访问资源无权限时的处理类

    import com.example.demo.two.controller.vo.R;
    import com.example.demo.two.util.SimpleResponseUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.web.access.AccessDeniedHandler;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.Resource;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @Slf4j
    @Component
    public class MyAccessDeniedHandler implements AccessDeniedHandler {
        @Resource
        private SimpleResponseUtil simpleResponseUtil;
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
            // 无权限,一般返回403
            log.info("拒绝访问:{}", accessDeniedException.getMessage());
            simpleResponseUtil.write(response, R.fail("拒绝访问"));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    未登录访问资源时的处理类

    import com.example.demo.two.controller.vo.R;
    import com.example.demo.two.util.SimpleResponseUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.AuthenticationEntryPoint;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.Resource;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @Slf4j
    @Component
    public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
        @Resource
        private SimpleResponseUtil simpleResponseUtil;
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            // 未认证,一般返回401
            log.info("未认证: {}", authException.getMessage());
            simpleResponseUtil.write(response, R.fail("未认证,请先认证"));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    自定义过滤器,以便识别用户身份,把用户身份设置到线程上下文中

    import com.example.demo.two.controller.LoginController;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import org.springframework.web.filter.OncePerRequestFilter;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.Objects;
    
    @Slf4j
    @Component
    public class AuthFilter extends OncePerRequestFilter {
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            // 比如请求头中有个header叫token,放置了认证后的请求头
            String token = request.getHeader("token");
            log.info("用户token:{}", token);
            if (StringUtils.hasText(token)) {
                // 验证token是否已经登录了的用户的token,用户的token临时放在了LoginController
                UserDetails userDetails = LoginController.TOKEN_USERNAME.get(token);
                if (Objects.nonNull(userDetails)) {
                    // 有,表示token是对的,设置线程上下文认证信息,然后访问其他资源时,security就会放行
                    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
            }
            filterChain.doFilter(request, response);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    验证效果

    未认证前访问首页:
    在这里插入图片描述
    在这里插入图片描述

    登录认证:

    在这里插入图片描述
    认证成功后,把得到的token放入请求头中,再请求
    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    初识云计算
    Mysql存储函数
    Linux程序之可变参数&&选项那些事!
    win11怎么把c盘移到d盘?
    库存三层模型(中)
    dbForge SQL Complete据库范围的配置
    【IoT毕设.下】STM32+机智云AIoT+实验室安全监控系统
    【面试专题】设计模式篇①
    Vue中的v-show和v-if指令的区别是什么?
    Flutter高仿微信-第35篇-单聊-视频通话
  • 原文地址:https://blog.csdn.net/q4444tita/article/details/133420966