文章详情

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

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-04-02 19:55

关注

前言

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(默认)

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 个方法:

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

@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

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯