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.boot</groupId>
     <artifactId>spring-boot-starter-security</artifactId>
</dependency>

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();
    }

}

放开/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 "首页";
    }
}

简单的认证接口,注入了用户查询服务和密码加密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("拒绝访问"));
    }
}

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

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("未认证,请先认证"));
    }
}

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

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);
    }
}

验证效果

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

登录认证:

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值