当前位置: 首页 > article >正文

Spring Security 快速开始

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

一、认证

1、从数据中读数据完成认证

@Service
public class MyUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws AuthenticationException {
        MyUser myUser;
        // 这里模拟从数据库中获取用户信息
        if (username.equals("admin")) {
            myUser = new MyUser("admin", new BCryptPasswordEncoder().encode("123456"), new ArrayList<>(Arrays.asList("p1", "p2")));
            return myUser;
        } else {
            throw new UsernameNotFoundException("用户不存在");
        }
    }
}

集成User可以补充一些自己的数据,修改构造方法(或者直接使用User)

public class MyUser extends User {

    private int sex;
    private int age;
    private String address;
    public MyUser(String username, String password, List<String> authorities) {
        super(username, password, authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
    }

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

相关配置

@Configuration
public class MySecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

2、实现同时多种认证方式

在1的基础上添加相关配置

/**
 * 手机验证码认证信息,在UsernamePasswordAuthenticationToken的基础上添加属性 手机号、验证码
 */
public class MobilecodeAuthenticationToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = 530L;
    private Object principal;
    private Object credentials;
    private String phone;
    private String mobileCode;


    public MobilecodeAuthenticationToken(String phone, String mobileCode) {
        super(null);
        this.phone = phone;
        this.mobileCode = mobileCode;
        this.setAuthenticated(false);
    }

    public MobilecodeAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true);
    }

    public Object getCredentials() {
        return this.credentials;
    }

    public Object getPrincipal() {
        return this.principal;
    }

    public String getPhone() {
        return phone;
    }

    public String getMobileCode() {
        return mobileCode;
    }

    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        } else {
            super.setAuthenticated(false);
        }
    }

    public void eraseCredentials() {
        super.eraseCredentials();
        this.credentials = null;
    }
}
public class MobilecodeAuthenticationProvider implements AuthenticationProvider {

    private UserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        MobilecodeAuthenticationToken mobilecodeAuthenticationToken = (MobilecodeAuthenticationToken) authentication;
        String phone = mobilecodeAuthenticationToken.getPhone();
        String mobileCode = mobilecodeAuthenticationToken.getMobileCode();
        System.out.println("登陆手机号:" + phone);
        System.out.println("手机验证码:" + mobileCode);

        // 模拟从redis中读取手机号对应的验证码及其用户名
        Map<String,String> dataFromRedis = new HashMap<>();
        dataFromRedis.put("code", "6789");
        dataFromRedis.put("username", "admin");

        // 判断验证码是否一致
        if (!mobileCode.equals(dataFromRedis.get("code"))) {
            throw new BadCredentialsException("验证码错误");
        }

        // 如果验证码一致,从数据库中读取该手机号对应的用户信息
        MyUser loadedUser = (MyUser) userDetailsService.loadUserByUsername((String)dataFromRedis.get("username"));
        if (loadedUser == null) {
            throw new UsernameNotFoundException("用户不存在");
        } else {
            MobilecodeAuthenticationToken result = new MobilecodeAuthenticationToken(loadedUser, null, loadedUser.getAuthorities());
            return result;
        }
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return MobilecodeAuthenticationToken.class.isAssignableFrom(aClass);
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
}

修改配置文件

@Configuration
public class MySecurityConfigurer extends WebSecurityConfigurerAdapter {
    

    @Autowired
    private UserDetailsService myUserDetailsService;


    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public MobilecodeAuthenticationProvider mobilecodeAuthenticationProvider() {
        MobilecodeAuthenticationProvider mobilecodeAuthenticationProvider = new MobilecodeAuthenticationProvider();
        mobilecodeAuthenticationProvider.setUserDetailsService(myUserDetailsService);
        return mobilecodeAuthenticationProvider;
    }

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        daoAuthenticationProvider.setUserDetailsService(myUserDetailsService);
        return daoAuthenticationProvider;
    }

    /**
     * 定义认证管理器AuthenticationManager
     * @return
     */
    @Bean
    public AuthenticationManager authenticationManager() {
        List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
        authenticationProviders.add(mobilecodeAuthenticationProvider());
        authenticationProviders.add(daoAuthenticationProvider());
        ProviderManager authenticationManager = new ProviderManager(authenticationProviders);
//        authenticationManager.setEraseCredentialsAfterAuthentication(false);
        return authenticationManager;

    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                // 关闭csrf
                .csrf().disable()
                
                // 权限配置,登录相关的请求放行,其余需要认证
                .authorizeRequests()
                .antMatchers("/login/*").permitAll()
                .anyRequest().authenticated();
    }
}

发送登录请求

@RestController
@RequestMapping("/login")
public class TestController {

    @GetMapping("/hello")
    public String hello(){
        return "Hello Spring security";
    }


    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 用户名密码登录
     * @param username
     * @param password
     * @return
     */
    @GetMapping("/usernamePwd")
    public Result usernamePwd(String username, String password) {
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        Authentication authenticate = null;
        try {
            authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error("登陆失败");
        }

        String token = UUID.randomUUID().toString().replace("-", "");
        return Result.ok(token);
    }

    /**
     * 手机验证码登录
     * @param phone
     * @param mobileCode
     * @return
     */
    @GetMapping("/mobileCode")
    public Result mobileCode(String phone, String mobileCode) {
        MobilecodeAuthenticationToken mobilecodeAuthenticationToken = new MobilecodeAuthenticationToken(phone, mobileCode);
        Authentication authenticate = null;
        try {
            authenticate = authenticationManager.authenticate(mobilecodeAuthenticationToken);
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error("验证码错误");
        }

        String token = UUID.randomUUID().toString().replace("-", "");
        return Result.ok(token);
    }
}

 3、用token 校验过滤

 认证成功后通常会返回一个token令牌(如jwt等),后续我们将token放到请求头中进行请求,后端校验该token,校验成功后再访问相应的接口

@Component
@WebFilter
public class TokenAuthenticationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        String token = httpServletRequest.getHeader("token");

        // 如果没有token,跳过该过滤器
        if (!StringUtils.isEmpty(token)) {
            // 模拟redis中的数据
            Map map = new HashMap();
            map.put("test_token1", new MyUser("admin", new BCryptPasswordEncoder().encode("123456"), new ArrayList<>(Arrays.asList("p1", "p2"))));
            map.put("test_token2", new MyUser("root", new BCryptPasswordEncoder().encode("123456"), new ArrayList<>(Arrays.asList("p1", "p2"))));

            // 这里模拟从redis获取token对应的用户信息
            MyUser myUser = (MyUser)map.get(token);
            if (myUser != null) {
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(myUser, null, myUser.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authRequest);
            } else {
                throw new PreAuthenticatedCredentialsNotFoundException("token不存在");
            }
        }

        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
}

 修改配置 MySecurityConfigurer

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                // 关闭csrf
                .csrf().disable()

                // 权限配置,登录相关的请求放行,其余需要认证
                .authorizeRequests()
                .antMatchers("/login/*").permitAll()
                .anyRequest().authenticated()
                .and()
                // 添加token认证过滤器
                .addFilterAfter(tokenAuthenticationFilter, LogoutFilter.class)
                // 不使用session会话管理
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

二、认证

1、基于方法的授权

当我们想要开启spring方法级安全时,只需要在任何 @Configuration实例上使用@EnableGlobalMethodSecurity 注解就能达到此目的。同时这个注解为我们提供了prePostEnabled 、securedEnabled 和 jsr250Enabled 三种不同的机制来实现同一种功能。

    @GetMapping("/hello")
    @PreAuthorize("hasAuthority('cece2')")
    public String hello(){
        return "Hello Spring security";
    }

 也可以

@PreAuthorize("@el.verify('cece2')")  然后往容器中添加el 实例,然后使用自定义的方法验证权限

2、基于Request的授权

http.authorizeRequests(
       authorize -> authorize
             // 添加授权配置
             .requestMatchers("/user/list").hasAnyAuthority("USER_LIST")
             .requestMatchers("/user/add").hasAnyAuthority("USER_ADD")

             // 对所有请求开启授权保护
             .anyRequest()
             // 已认证的请求会被自动授权
             .authenticated()
       )

三、相关配置

Security 常用配置-CSDN博客

四:参考

Spring Security入门教程

​​​​​​Spring Security 实现多种认证方式


http://www.kler.cn/a/301693.html

相关文章:

  • 动态规划之股票系列
  • 基于Springboot+Vue的中国蛇类识别系统 (含源码数据库)
  • ONLYOFFICE8.2版本测评,团队协作的办公软件
  • 详解map与multimap容器
  • 移除元素(leetcode 27)
  • 丹摩征文活动 |【前端开发】HTML+CSS+JavaScript前端三剑客的基础知识体系了解
  • centos7安装MySQL5.7.44
  • Docker Swarm管理(Docker技术集群与应用)
  • k8s的配置
  • 【网络安全】漏洞挖掘之CVE-2019-9670+检测工具
  • 如何使用Flask渲染模板
  • 比 GPT-4 便宜 187 倍的Mistral 7B (非广告)
  • 明基相机sd卡格式化还能恢复数据吗?可以这样操作
  • 漫谈设计模式 [16]:中介者模式
  • L3学习打卡笔记
  • QT进行音频录制
  • elementUI中el-form 嵌套el-from 如何进行表单校验?
  • 【C++ 智能指针】RAII和四种智能指针你理解吗?
  • Python学习笔记--类型、运算符、循环
  • 物联网之PWM呼吸灯、脉冲、LEDC
  • 【Linux】 LTG:移动硬盘部署Ubuntu24.04
  • vue 父组件给子组件传值
  • 基于深度学习的创新设计的生成AI
  • 哪一种反爬虫策略更加人性化,不让用户感知到
  • 02.06、回文链表
  • 离散制造与流程制造的差异分析