我的编程空间,编程开发者的网络收藏夹
学习永远不晚

SpringBoot Security从入门到实战示例教程

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

SpringBoot Security从入门到实战示例教程

前言

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。提供了完善的认证机制和方法级的授权功能。是一款非常优秀的权限管理框架。它的核心是一组过滤器链,不同的功能经由不同的过滤器。这篇文章给大家讲解SpringBoot Security从入门到实战,内容如下所示:

入门

测试接口

假设我们用下面的接口做权限测试。

@RestController
public class LakerController {
    @GetMapping("/laker")
    public String laker() {
        return IdUtil.simpleUUID();
    }
    @GetMapping("/laker/q")
    public String lakerQ() {
        return IdUtil.simpleUUID();
    }
}

浏览器访问:http://localhost:8080/laker,结果如下:

增加依赖

pom.xml,添加 spring-boot-starter-securtiy 依赖。

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

再次访问http://localhost:8080/laker,结果如下:

简要解析

我们访问http://localhost:8080/laker,security判断我们没有登录,则会302重定向http://localhost:8080/login(默认)

  • security会返回一个默认的登录页。
  • 默认用户名为:user,密码在服务启动时,会随机生成一个,可以查看启动日志如下:

2022-05-02 21:01:03.697  INFO 17896 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2022-05-02 21:01:03.825  INFO 17896 --- [           main] .s.s.UserDetailsServiceAutoConfiguration : 

Using generated security password: e53fef6a-3f61-43c3-9609-ce88fd7c0841

当然,可以通过配置文件设置默认的用户名、密码、角色。

spring:
  security:
    user:
      # 默认是 user
      name: laker
      password: laker
      roles:
        - ADMIN
        - TESTER

以上的默认都是可以修改的哈。

判断是否登录使用传统的cookie session模式哈,JSESSIONID E3512CD1A81DB7F2144C577BA38D2D92 HttpOnly true

自定义配置

实际项目中我们的用户、密码、角色、权限、资源都是存储在数据库中的,我们可以通过自定义类继承 WebSecurityConfigurerAdapter,从而实现对 Spring Security 更多的自定义配置。

@Configuration
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
		...
}

配置密码加密方式

必须配置,否则报空指针异常。

   @Bean
    PasswordEncoder passwordEncoder() {
        // 不加密
        return NoOpPasswordEncoder.getInstance();
    }

Spring Security 提供了多种密码加密方案,官方推荐使用 BCryptPasswordEncoder

BCryptPasswordEncoder 使用 BCrypt 强哈希函数,开发者在使用时可以选择提供 strengthSecureRandom 实例。strength 取值在 4~31 之间(默认为 10)。strength 越大,密钥的迭代次数越多(密钥迭代次数为 2^strength)。如果是数据库认证,库里的密码同样也存放加密后的密码。同一密码每次 Bcrypt 生成的结果都会变化不会重复。

配置AuthenticationManagerBuilder 认证用户、角色权限

支持直接配置内存认证模式和配置UserDetailsServiceBean方式

内存认证模式,实际项目不用这个哦。(仅做了解)

 
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin").password("123").roles("ADMIN", "USER")
                .and()
                .withUser("laker").password("123").roles("USER");
    }

上面在UserDetailsService的实现之一InMemoryUserDetailsManager中,即存储在内存中。

自定义UserDetailsServiceBean方式,实际项目都是使用这个,可定制化程度高。

步骤一:定义一个LakerUserService实现UserDetailsService,配置成SpringBean。该方法将在用户登录时自动调用。

@Service
public class LakerUserService implements UserDetailsService {
    @Autowired
    UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // username 就是前端传递的例如 laker 123,即 laker
        User user = userMapper.loadUserByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("账户不存在!");
        }
        user.setAuthorities(...);
        return user;
    }
}

username

password(加密后的密码)

authorities

返回的Bean中以上3个都不能为空。返回User后由系统提供的 DaoAuthenticationProvider 类去比对密码是否正确。

Spring Security默认支持表单请求登录的源码,UsernamePasswordAuthenticationFilter.java

步骤二:在把自定义的LakerUserService装载进去.

@Autowired
UserService userService;
...
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userService);
}

步骤三:其中我们的业务用户User必须要实现UserDetails接口,并实现该接口中的 7 个方法:

  • getAuthorities():获取当前用户对象所具有的权限信息
  • getPassword():获取当前用户对象的密码

返回的密码和用户输入的登录密码不匹配,会自动抛出 BadCredentialsException 异常。

  • getUsername():获取当前用户对象的用户名
  • isAccountNonExpired():当前账户是否未过期
  • isAccountNonLocked():当前账户是否未锁定
  • 返回了 false,会自动抛出 AccountExpiredException 异常。
  • isCredentialsNonExpired():当前账户密码是否未过期
  • isEnabled():当前账户是否可用
@Data
public class User implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    private Boolean enabled;
    private Boolean locked;
    private List<String> authorities;
    @Override
    public String getPassword() {
        return password;
    }
    @Override
    public String getUsername() {
        return username;
    }
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return !locked;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return enabled;
    }
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authoritiesList = new ArrayList<>();
        for (String authority : authorities) {
            authoritiesList.add(new SimpleGrantedAuthority(authority));
        }
        return authoritiesList;
    }
}

配置HttpSecurity Url访问权限

  
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //
        http    // 1.开启 HttpSecurity 配置
                .authorizeRequests()
                // laker
            //本次要访问的资源
              SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(request.getMethod() + "" + request.getRequestURI());

            //用户拥有的权限中是否包含请求的url
            return userDetails.getAuthorities().contains(simpleGrantedAuthority);
        }
        return false;
    }
        public boolean hasPermission() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Object principal = authentication.getPrincipal();
        if (principal instanceof UserDetails) {
            UserDetails userDetails = (UserDetails) principal;
            
            //本次要访问的资源
            HttpServletRequest request =((ServletRequestAttributes)
                    RequestContextHolder.getRequestAttributes()).getRequest();

            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(request.getRequestURI());
            //用户拥有的权限中是否包含请求的url
            return userDetails.getAuthorities().contains(simpleGrantedAuthority);
        }
        return false;
    }
}
// controller方法
@PreAuthorize("@rbacService.hasPermission()")
public String test() {
}
// 或者高级的全局url鉴权
public class SecurityConfig extends WebSecurityConfigurerAdapter {
         ...
      http.authorizeRequests() //设置授权请求,任何请求都要经过下面的权限表达式处理
          .anyRequest().access("@rbacService.hasPermission(request,authentication)") //权限表达式     

3.扩展默认方法自定义扩展根对象SecurityExpressionRoot

https://www.jb51.net/article/245172.htm

1.创建自定义根对象

public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
    public CustomMethodSecurityExpressionRoot(Authentication authentication) {
        super(authentication);
    }
    
    public boolean hasUser(String... username) {
        String name = this.getAuthentication().getName();
        HttpServletRequest request = ((ServletRequestAttributes)
                RequestContextHolder.getRequestAttributes()).getRequest();
        String[] names = username;
        for (String nameStr : names) {
            if (name.equals(nameStr)) {
                return true;
            }
        }
        return false;
    }
}

2.创建自定义处理器

创建自定义处理器,主要是重写创建根对象的方法。

public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
    @Override
    protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
        CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication);
        root.setThis(invocation.getThis());
        root.setPermissionEvaluator(getPermissionEvaluator());
        root.setTrustResolver(getTrustResolver());
        root.setRoleHierarchy(getRoleHierarchy());
        root.setDefaultRolePrefix(getDefaultRolePrefix());
        return root;
    }
}

3.配置GlobalMethodSecurityConfiguration
之前我们使用@EnableGlobalMethodSecurity开启全局方法安全,而这些全局方法级别的安全配置就在GlobalMethodSecurityConfiguration配置类中。

可以扩展这个类来自定义默认值,但必须确保在类上指定@EnableGlobalMethodSecurity 注解,否则会bean冲突报错。

@Configuration
// 将EnableGlobalMethodSecurity注解移到这里
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        return new CustomMethodSecurityExpressionHandler();
    }
}

4.controller使用自定义方法

@PreAuthorize("hasUser('laker','admin')")
public String test() {
    ...
}

登出

http.logout().logoutUrl("/logout").logoutSuccessHandler((request, response, authentication) -> {
            // 删除用户token
    		...
            // 返回json
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_OK);
            PrintWriter out = response.getWriter();
            out.write("OK");
            out.flush();
            out.close();
        });

跨域

@Override
protected void configure(HttpSecurity http) throws Exception {
	http.cors();//允许跨域,配置后SpringSecurity会自动寻找name=corsConfigurationSource的Bean
	http.csrf().disable();//关闭CSRF防御
}

@Configuration
public class CrosConfig {
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration cores=new CorsConfiguration();
        cores.setAllowCredentials(true);//允许客户端携带认证信息
        //springBoot 2.4.1版本之后,不可以用 * 号设置允许的Origin,如果不降低版本,则在跨域设置时使用setAllowedOriginPatterns方法
       // cores.setAllowedOrigins(Collections.singletonList("*"));//允许所有域名可以跨域访问
        cores.setAllowedOriginPatterns(Collections.singletonList("*"));
        cores.setAllowedMethods(Arrays.asList("GET","POST","DELETE","PUT","UPDATE"));//允许哪些请求方式可以访问
        cores.setAllowedHeaders(Collections.singletonList("*"));//允许服务端访问的客户端请求头
        // 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
        cores.addExposedHeader(jsonWebTokenUtil.getHeader());
        // 注册跨域配置
        // 也可以使用CorsConfiguration 类的 applyPermitDefaultValues()方法使用默认配置
        source.registerCorsConfiguration("
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //
        http    // 1.过滤请求
                .authorizeRequests()
                // 2.对于登录login 验证码captcha 允许访问
                .antMatchers("/login").permitAll()
                // 用户访问其它URL都必须认证后访问(登录后访问)
                .anyRequest().authenticated()
                .and()
                // 3.关闭csrf
                .csrf().disable()
                // 4.基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                // 5.页面能不能以 frame、 iframe、 object 形式嵌套在其他站点中,用来避免点击劫持(clickjacking)攻击
                .and().headers().frameOptions().disable();
        // 异常处理
        http.exceptionHandling()
                // 未认证返回401
                .authenticationEntryPoint((req, response, authException) -> {
                    response.setContentType("application/json;charset=utf-8");
                    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    PrintWriter out = response.getWriter();
                    out.write("尚未登录,请先登录");
                    out.flush();
                    out.close();
                })
                // 没有权限,返回403 json
                .accessDeniedHandler((request, response, ex) -> {
                    response.setContentType("application/json;charset=utf-8");
                    response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                    PrintWriter out = response.getWriter();
                    Map<String, Object> map = new HashMap<String, Object>();
                    map.put("code", 403);
                    map.put("message", "权限不足");
                    out.write(JSONUtil.toJsonPrettyStr(map));
                    out.flush();
                    out.close();
                });
        // 配置登出
        http.logout().logoutUrl("/logout").logoutSuccessHandler((request, response, authentication) -> {
                // 删除用户token
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_OK);
            PrintWriter out = response.getWriter();
            out.write("OK");
            out.flush();
            out.close();
        });
        // 添加JWT filter
        http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }
    
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/css
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception
    {
        return super.authenticationManagerBean();
    }
}

参考:

https://blog.csdn.net/X_lsod/article/details/122914659

https://blog.csdn.net/godleaf/article/details/108318403

https://blog.csdn.net/qq_43437874/article/details/119543579

到此这篇关于SpringBoot Security从入门到实战示例教程的文章就介绍到这了,更多相关SpringBoot Security入门内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

SpringBoot Security从入门到实战示例教程

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

JavaScript Vue.js 实战教程:从入门到精通

,通过一系列渐进式的示例,让读者从零开始学习 Vue.js,掌握其核心概念和基本用法,并最终能够利用 Vue.js 构建动态交互的前端应用。
JavaScript Vue.js 实战教程:从入门到精通
2024-02-04

Android SDK安装教程(超详细),从零基础入门到实战,从看这篇开始

前言 在使用appnium的时候,除了安装JDK之外,也需要安装Android SDK。那么,正确安装Android SDK是怎样的呢,跟着小编继续往下看。 安装Android SDK和环境配置 1.安装Android SDK
2023-08-16

数据库水平分割实战指南:从入门到精通的完整教程

数据库水平分割是一种有效的优化技术,它可以将大型数据库拆分为多个较小的数据库,从而提高查询性能和可扩展性。本文将介绍如何进行数据库水平分割,从入门到精通,循序渐进,帮助您快速掌握这一技术。
数据库水平分割实战指南:从入门到精通的完整教程
2024-02-24

价值8000元的Linux教程免费送|千锋出品《Shell编程从入门到实战》

价值8000元的Linux教程免费送|千锋出品《Shell编程从入门到实战》学习运维的,多多少少都绕不过Linux,想要从事运维工作,首要条件是掌握过硬的Linux技术,而Linux运维所涉及到的包括网络设备、安全设备、网络构建等等多方面知
2023-06-04

HTML 多媒体标签实战教程:从入门到精通,打造视听震撼的网页

掌握 HTML 多媒体标签的使用技巧,让您的网页充满活力与吸引力,用视听交互的魅力征服访客,打造出让人印象深刻的网上世界。
HTML 多媒体标签实战教程:从入门到精通,打造视听震撼的网页
2024-02-08

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录