作者:清茶淡粥酱\
链接:https://juejin.cn/post/702673...

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-starter-security启动器后,Spring Security曾经失效,默认拦挡全副申请,如果用户没有登录,跳转到内置登录页面。

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

用户名: user

明码:我的项目启动,打印在控制台中

自定义用户名和明码

批改application.yml 文件

# 动态用户,个别只在外部网络认证中应用,如:外部服务器1,拜访服务器2spring:  security:    user:      name: test  # 通过配置文件,设置动态用户名      password: test # 配置文件,设置动态登录明码

UserDetailsService详解

什么也没有配置的时候,账号和明码是由Spring Security定义生成的。而在理论我的项目中账号和明码都是从数据库中查问进去的。 所以咱们要通过自定义逻辑管制认证逻辑。如果须要自定义逻辑时,只须要实现UserDetailsService接口

@Componentpublic 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...

PasswordEncoder明码解析器详解

PasswordEncoder

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

SpringSecurity 定义了很多实现接口PasswordEncoder 满足咱们明码加密、明码校验 应用需要

自定义明码解析器

  1. 编写类,实现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();   */@Beanpublic 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@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    @Autowired    private  UserSecurity userSecurity;    @Autowired    private PersistentTokenRepository persistentTokenRepository;    /**     * 加密     * @return 加密对象     * 如需应用自定义加密逻辑 返回自定义加密对象     * 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/readhttp.authorizeRequests().antMatchers("/guest/read").hasAnyRole("管理员", "访客")
hasIpAddress(String) 申请是指定的IP就运行拜访
//ip 是127.0.0.1 的申请 能够拜访/iphttp.authorizeRequests().antMatchers("/ip").hasIpAddress("127.0.0.1")

403 权限有余页面解决

1.编写类实现接口AccessDeniedHandler

/** * @describe  403 权限有余 * @author: AnyWhere * @date 2021/4/18 20:57 */@Componentpublic 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 style='width:800px;text-align:center;margin:auto;font-size:24px'>" +                        "权限有余,请分割管理员" +                        "</div>" +                        "</body>" +                        "</html>"        );        response.getWriter().flush();//刷新缓冲区    }}

2.配置类中配置exceptionHandling

// 配置403拜访谬误处理器。http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);/

RememberMe(记住我)

@Configuration@EnableWebSecuritypublic 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开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞+转发哦!