Spring Security 最佳实践,看了必懂!()

  本篇文章为你整理了Spring Security 最佳实践,看了必懂!()的详细内容,包含有 Spring Security 最佳实践,看了必懂!,希望能帮助你了解 Spring Security 最佳实践,看了必懂!。

  分享Java技术,高并发编程,分布式技术,架构设计,Java面试题,算法,行业动态,程序人生等。

  
作者:清茶淡粥酱
 

  链接:https://juejin.cn/post/7026734817853210661

  Spring Security简介

  Spring Security 是一种高度自定义的安全框架,利用(基于)SpringIOC/DI和AOP功能,为系统提供了声明式安全访问控制功能,减少了为系统安全而编写大量重复代码的工作。

  核心功能:认证和授权

  Spring Security 认证流程

  Spring Security 项目搭建

  Spring Security已经被Spring boot进行集成,使用时直接引入启动器即可

  

 dependency 

 

   groupId org.springframework.boot /groupId

   artifactId spring-boot-starter-security /artifactId

   /dependency

  

 

  Spring Boot 基础就不介绍了,推荐下这个实战教程:

  https://github.com/javastacks/spring-boot-best-practice

  导入spring-boot-starter-security启动器后,Spring Security已经生效,默认拦截全部请求,如果用户没有登录,跳转到内置登录页面。

  在浏览器输入:http://localhost:8080/ 进入Spring Security内置登录页面

  用户名: user

  密码:项目启动,打印在控制台中

  自定义用户名和密码

  修改application.yml 文件

  

# 静态用户,一般只在内部网络认证中使用,如:内部服务器1,访问服务器2

 

  spring:

   security:

   user:

   name: test # 通过配置文件,设置静态用户名

   password: test # 配置文件,设置静态登录密码

  

 

  UserDetailsService详解

  什么也没有配置的时候,账号和密码是由Spring Security定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。如果需要自定义逻辑时,只需要实现UserDetailsService接口

  

@Component

 

  public class UserSecurity implements UserDetailsService {

   @Autowired

   private UserService userService;

   @Override

   public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {

   User user = userService.login(userName);

   System.out.println(user);

   if (null==user){

   throw new UsernameNotFoundException("用户名错误");

   org.springframework.security.core.userdetails.User result =

   new org.springframework.security.core.userdetails.User(

   userName,user.getPassword(), AuthorityUtils.createAuthorityList()

   return result;

  

 

  推荐一个 Spring Boot 基础教程:

  https://github.com/javastacks/spring-boot-best-practice

  PasswordEncoder密码解析器详解

  PasswordEncoder

  PasswordEncoder 是SpringSecurity 的密码解析器,用户密码校验、加密 。 自定义登录逻辑时要求必须给容器注入PaswordEncoder的bean对象

  SpringSecurity 定义了很多实现接口PasswordEncoder 满足我们密码加密、密码校验 使用需求

  自定义密码解析器

  编写类,实现PasswordEncoder 接口

  

/**

 

   * 凭证匹配器,用于做认证流程的凭证校验使用的类型

   * 其中有2个核心方法

   * 1. encode - 把明文密码,加密成密文密码

   * 2. matches - 校验明文和密文是否匹配

   * */

  public class MyMD5PasswordEncoder implements PasswordEncoder {

   * 加密

   * @param charSequence 明文字符串

   * @return

   @Override

   public String encode(CharSequence charSequence) {

   try {

   MessageDigest digest = MessageDigest.getInstance("MD5");

   return toHexString(digest.digest(charSequence.toString().getBytes()));

   } catch (NoSuchAlgorithmException e) {

   e.printStackTrace();

   return "";

   * 密码校验

   * @param charSequence 明文,页面收集密码

   * @param s 密文 ,数据库中存放密码

   * @return

   @Override

   public boolean matches(CharSequence charSequence, String s) {

   return s.equals(encode(charSequence));

   * @param tmp 转16进制字节数组

   * @return 饭回16进制字符串

   private String toHexString(byte [] tmp){

   StringBuilder builder = new StringBuilder();

   for (byte b :tmp){

   String s = Integer.toHexString(b 0xFF);

   if (s.length()==1){

   builder.append("0");

   builder.append(s);

   return builder.toString();

  

 

  2.在配置类中指定自定义密码凭证匹配器

  

/**

 

   * 加密

   * @return 加密对象

   * 如需使用自定义密码凭证匹配器 返回自定义加密对象

   * 例如: return new MD5PasswordEncoder();

  @Bean

  public PasswordEncoder passwordEncoder() {

   return new BCryptPasswordEncoder(); //Spring Security 自带

  

 

  方式一 转发

  

http.formLogin()

 

   .usernameParameter("name") // 设置请求参数中,用户名参数名称。 默认username

   .passwordParameter("pswd") // 设置请求参数中,密码参数名称。 默认password

   .loginPage("/toLogin") // 当用户未登录的时候,跳转的登录页面地址是什么? 默认 /login

   .loginProcessingUrl("/login") // 用户登录逻辑请求地址是什么。 默认是 /login

   .failureForwardUrl("/failure"); // 登录失败后,请求转发的位置。Security请求转发使用Post请求。默认转发到: loginPage?error

   .successForwardUrl("/toMain"); // 用户登录成功后,请求转发到的位置。Security请求转发使用POST请求。

  

 

  方式二 :重定向

  

http.formLogin()

 

   .usernameParameter("name") // 设置请求参数中,用户名参数名称。 默认username

   .passwordParameter("pswd") // 设置请求参数中,密码参数名称。 默认password

   .loginPage("/toLogin") // 当用户未登录的时候,跳转的登录页面地址是什么? 默认 /login

   .loginProcessingUrl("/login") // 用户登录逻辑请求地址是什么。 默认是 /login

   .defaultSuccessUrl("/toMain",true); //用户登录成功后,响应重定向到的位置。 GET请求。必须配置绝对地址。

   .failureUrl("/failure"); // 登录失败后,重定向的位置。

  

 

  方式三:自定义登录处理器

  自定义登录失败逻辑处理器

  

/*自定义登录失败处理器*/

 

  public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

   private String url;

   private boolean isRedirect;

  
public MyAuthenticationFailureHandler(String url, boolean isRedirect) {

   this.url = url;

   this.isRedirect = isRedirect;

   @Override

   public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {

   if (isRedirect){

   httpServletResponse.sendRedirect(url);

   }else {

   httpServletRequest.getRequestDispatcher(url).forward(httpServletRequest,httpServletResponse);

  //get set 方法 省略

  

 

  自定义登录成功逻辑处理器

  

/**

 

   * 自定义登录成功后处理器

   * 转发重定向,有代码逻辑实现

   * */

  public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

   private String url;

   private boolean isRedirect;

   public MyAuthenticationSuccessHandler(String url, boolean isRedirect) {

   this.url = url;

   this.isRedirect = isRedirect;

   * @param request 请求对象 request.getRequestDispatcher.forward()

   * @param response 响应对象 response.sendRedirect()

   * @param authentication 用户认证成功后的对象。其中报换用户名权限结合,内容是

   * 自定义UserDetailsService

   * */

   @Override

   public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

   if (isRedirect){

   response.sendRedirect(url);

   }else {

   request.getRequestDispatcher(url).forward(request,response);

  //get set 方法 省略

  http.formLogin()

   .usernameParameter("name") // 设置请求参数中,用户名参数名称。 默认username

   .passwordParameter("pswd") // 设置请求参数中,密码参数名称。 默认password

   .loginPage("/toLogin") // 当用户未登录的时候,跳转的登录页面地址是什么? 默认 /login

   .loginProcessingUrl("/login") // 用户登录逻辑请求地址是什么。 默认是 /login

  

 

  登录相关配置类

  

@Configuration

 

  @EnableWebSecurity

  public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Autowired

   private UserSecurity userSecurity;

   @Autowired

   private PersistentTokenRepository persistentTokenRepository;

  
* 如需使用自定义加密逻辑 返回自定义加密对象

   * return new MD5PasswordEncoder(); return new SimplePasswordEncoder();

   @Bean

   public PasswordEncoder passwordEncoder() {

   return new BCryptPasswordEncoder(); //Spring Security 自带

   @Override

   protected void configure(HttpSecurity http) throws Exception {

   // 配置登录请求相关内容。

   http.formLogin()

   .loginPage("/toLogin") // 当用户未登录的时候,跳转的登录页面地址是什么? 默认 /login

   .usernameParameter("name") // 设置请求参数中,用户名参数名称。 默认username

   .passwordParameter("pswd") // 设置请求参数中,密码参数名称。 默认password

   .loginProcessingUrl("/login") //设置登录 提交表单数据访问请求地址

   .defaultSuccessUrl("/toMain")

   .failureUrl("/toLogin");

   //.successForwardUrl("/toMain")

   //.failureForwardUrl("/toLogin");

   //.successHandler(new LoginSuccessHandler("/toMain", true)) //自定义登录成功处理器

   //.failureHandler(new LoginErrorHandler("/toLogin", true));

   http.authorizeRequests()

   //.antMatchers("/toLogin").anonymous() //只能匿名用户访问

   .antMatchers("/toLogin", "/register", "/login", "/favicon.ico").permitAll() // /toLogin请求地址,可以随便访问。

   .antMatchers("/**/*.js").permitAll() // 授予所有目录下的所有.js文件可访问权限

   .regexMatchers(".*[.]css").permitAll() // 授予所有目录下的所有.css文件可访问权限

   .anyRequest().authenticated(); // 任意的请求,都必须认证后才能访问。

  
http.logout()

   .invalidateHttpSession(true) // 回收HttpSession对象。退出之前调用HttpSession.invalidate() 默认 true

   .clearAuthentication(true) // 退出之前,清空Security记录的用户登录标记。 默认 true

   // .addLogoutHandler() // 增加退出处理器。

   .logoutSuccessUrl("/") // 配置退出后,进入的请求地址。 默认是loginPage?logout

   .logoutUrl("/logout"); // 配置退出登录的路径地址。和页面请求地址一致即可。

   // 关闭CSRF安全协议。

   // 关闭是为了保证完整流程的可用。

   http.csrf().disable();

  
@Bean

   public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){

   JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();

   jdbcTokenRepository.setDataSource(dataSource);

   //jdbcTokenRepository.setCreateTableOnStartup(true);

   return jdbcTokenRepository;

  

 

  hasAuthority(String) 判断角色是否具有特定权限

  

http.authorizeRequests().antMatchers("/main1.html").hasAuthority("admin")

 

  

 

  hasAnyAuthority(String ...) 如果用户具备给定权限中某一个,就允许访问

  

http.authorizeRequests().antMatchers("/admin/read").hasAnyAuthority("xxx","xxx") 

 

  

 

  hasRole(String) 如果用户具备给定角色就允许访问。否则出现403

  

//请求地址为/admin/read的请求,必须登录用户拥有管理员角色才可访问

 

  http.authorizeRequests().antMatchers("/admin/read").hasRole("管理员")

  

 

  hasAnyRole(String ...) 如果用户具备给定角色的任意一个,就允许被访问

  

//用户拥有角色是管理员 或 访客 可以访问 /guest/read

 

  http.authorizeRequests().antMatchers("/guest/read").hasAnyRole("管理员", "访客")

  

 

  hasIpAddress(String) 请求是指定的IP就运行访问

  

//ip 是127.0.0.1 的请求 可以访问/ip

 

  http.authorizeRequests().antMatchers("/ip").hasIpAddress("127.0.0.1")

  

 

  403 权限不足页面处理

  1.编写类实现接口AccessDeniedHandler

  

/**

 

   * @describe 403 权限不足

   * @author: AnyWhere

   * @date 2021/4/18 20:57

  @Component

  public class MyAccessDeniedHandler implements AccessDeniedHandler {

   @Override

   public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)

   throws IOException, ServletException {

   response.setStatus(HttpServletResponse.SC_OK);

   response.setContentType("text/html;charset=UTF-8");

   response.getWriter().write(

   " html " +

   " body " +

   " div " +

   "权限不足,请联系管理员" +

   " /div " +

   " /body " +

   " /html "

   response.getWriter().flush();//刷新缓冲区

  

 

  2.配置类中配置exceptionHandling

  

// 配置403访问错误处理器。

 

  http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);/

  

 

  RememberMe(记住我)

  

@Configuration

 

  @EnableWebSecurity

  public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Override

   protected void configure(HttpSecurity http) throws Exception {

   //配置记住密码

   http.rememberMe()

   .rememberMeParameter("remember-me") // 修改请求参数名。 默认是remember-me

   .tokenValiditySeconds(14*24*60*60) // 设置记住我有效时间。单位是秒。默认是14天

   .rememberMeCookieName("remember-me") // 修改remember me的cookie名称。默认是remember-me

   .tokenRepository(persistentTokenRepository) // 配置用户登录标记的持久化工具对象。

   .userDetailsService(userSecurity); // 配置自定义的UserDetailsService接口实现类对象

   @Bean

   public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){

   JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();

   jdbcTokenRepository.setDataSource(dataSource);

   //jdbcTokenRepository.setCreateTableOnStartup(true);

   return jdbcTokenRepository;

  

 

  Spring Security 注解

  @Secured

  角色校验 ,请求到来访问控制单元方法时必须包含XX角色才能访问

  角色必须添加ROLE_前缀

  

 @Secured({"ROLE_管理员","ROLE_访客"})

 

   @RequestMapping("/toMain")

   public String toMain(){

   return "main";

  

 

  使用注解@Secured需要在配置类中添加注解 使@Secured注解生效

  

@EnableGlobalMethodSecurity(securedEnabled = true)

 

  

 

  @PreAuthorize

  权限检验,请求到来访问控制单元之前必须包含xx权限才能访问,控制单元方法执行前进行角色校验

  

 /**

 

   * [ROLE_管理员, admin:read, admin:write, all:login, all:logout, all:error, all:toMain]

   * @PreAuthorize 角色 、权限 校验 方法执行前进行角色校验

   * hasAnyAuthority()

   * hasAuthority()

   * hasPermission()

   * hasRole()

   * hasAnyRole()

   * */

   @PreAuthorize("hasAnyRole(ROLE_管理员,ROLE_访客)")

   @RequestMapping("/toMain")

   @PreAuthorize("hasAuthority(admin:write)")

   public String toMain(){

   return "main";

  

 

  使用@PreAuthorize和@PostAuthorize 需要在配置类中配置注解@EnableGlobalMethodSecurity 才能生效

  

@EnableGlobalMethodSecurity(prePostEnabled = true)

 

  

 

  @PostAuthorize

  权限检验,请求到来访问控制单元之后必须包含xx权限才能访问 ,控制单元方法执行完后进行角色校验

  

 /**

 

   * [ROLE_管理员, admin:read, admin:write, all:login, all:logout, all:error, all:toMain]

   * @PostAuthorize 角色 、权限 校验 方法执行后进行角色校验

   * hasAnyAuthority()

   * hasAuthority()

   * hasPermission()

   * hasRole()

   * hasAnyRole()

   * */

   @PostAuthorize("hasRole(ROLE_管理员)")

   @RequestMapping("/toMain")

   @PreAuthorize("hasAuthority(admin:write)")

   public String toMain(){

   return "main";

  

 

  Spring Security 整合Thymeleaf 进行权限校验

  

 dependency 

 

   groupId org.springframework.boot /groupId

   artifactId spring-boot-starter-thymeleaf /artifactId

   /dependency

   dependency

   groupId org.thymeleaf.extras /groupId

   artifactId thymeleaf-extras-springsecurity5 /artifactId

   /dependency

  

 

  Spring Security中CSRF

  什么是CSRF?

  CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack” 或者Session Riding。通过伪造用户请求访问受信任站点的非法请求访问。

  跨域:只要网络协议,ip地址,端口中任何一个不相同就是跨域请求。

  客户端与服务进行交互时,由于http协议本身是无状态协议,所以引入了cookie进行记录客户端身份。在cookie中会存放session id用来识别客户端身份的。在跨域的情况下,session id可能被第三方恶意劫持,通过这个session id向服务端发起请求时,服务端会认为这个请求是合法的,可能发生很多意想不到的事情。

  通俗解释:

  CSRF就是别的网站非法获取我们网站Cookie值,我们项目服务器是无法区分到底是不是我们的客户端,只有请求中有Cookie,认为是自己的客户端,所以这个时候就出现了CSRF。

  近期热文推荐:

  1.1,000+ 道 Java面试题及答案整理(2022最新版)

  2.劲爆!Java 协程要来了。。。

  3.Spring Boot 2.x 教程,太全了!

  4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!

  5.《Java开发手册(嵩山版)》最新发布,速速下载!

  觉得不错,别忘了随手点赞+转发哦!

  以上就是Spring Security 最佳实践,看了必懂!()的详细内容,想要了解更多 Spring Security 最佳实践,看了必懂!的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: