深入浅出SpringSecurity读书笔记-第二章-SpringSecurity认证-01

快速入门:

        1.引入Spring Security依赖:

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

        2.在项目中编写一个用于测试的/hello接口:

@RestController
public class HelloController {

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

}

        3.测试访问

                url: http://localhost:8080/hello

                        默认的登录用户名是user,登录密码是一个随机生成的UUID字符串,在项目启动日志中可以看到登录密码(项目每次重新启动时都会发生变化)

                访问成功:

流程分析:

        (1)客户端(浏览器)发起请求去访问/hello接口,这个接口默认是需要认证之后才能访问的。

        (2)这个请求会走一遍Spring Security中的过滤器链,在最后的FilterSecurityInterceptor过滤器中被拦截下来,因为系统发现用户未认证。请求拦截下来之后,接下来会抛出AccessDeniedException异常

        (3)抛出的AccessDeniedException异常ExceptionTranslationFilter过滤器中被捕获,ExceptionTranslationFilter过滤器通过调用LoginUrlAuthenticationEntryPoint的commence方法给客户端返回502,要求客户端重定向到/login页面

        (4)客户端发送/login请求。

        (5)/login请求被DefaultLoginPageGeneratingFilter过滤器拦截下来,并在该过滤器中返回登录界面。所以用户访问/hello接口时首先看到登录页面。

        在整个过程中,相当于客户端一共发送了两个请求,第一个请求是/hello,服务端收到之后,返回302,要求客户端重定向到/login,于是客户端又发送了/login请求。

原理分析:

        在上述例子中,SpringBoot涉及到的操作:

        (1)开启Spring Security 自动化配置,开启后,会自动创建一个名为springSecurityFilterChain的过滤器,并注入到Spring容器中,这个过滤器将负责所有的安全管理,包括用户的认证、授权、重定向到登录页面等(springSecurityFilterChain实际上代理了SpringSecurity中的过滤链)

        (2)创建一个UserDetailsService实例,UserDetailsService负责提供用户数据,默认的用户数据是基于内存的用户,用户名为user,密码则是随机生成的UUID字符串

        (3)给用户生成一个默认的登录页面

        (4)开启CSRF攻击防御

        (5)开启会话固定攻击防御

        (6)集成X-XSS-Protection

        (7)集成X-Frame-Options以防止单击劫持

        默认用户生成:

                Spring Security中定义了UserDetails接口来规范开发者自定义的用户对象,这样方便一些旧系统、用户表已经固定的系统集成到Spring Security认证体系中。

                UserDetails接口定义:

public interface UserDetails extends Serializable{
    //获取用户的权限
    Collection<? extends GrantedAuthority> getAuthorities();
    //返回当前账户的密码
    String getPassword();
    //返回当前账户的用户名
    String getUsername();
    //返回当前账户是否未过期
    boolean isAccountNonExpired();
    //返回当前账户是否未锁定
    boolean isAccountNonLocked();
    //返回当前账户凭证(如密码)是否未过期
    boolean isCredentialsNonExpired();
    //返回当前账户是否可用
    boolean isEnabled();
}

                UserDetailsService(提供用户数据源)接口定义:

public interface UserDetailsService{
    //这里的参数username,不一定是通过登录表单输入的用户名,在CAS单点登录时,这里是CAS Server认证成功后回调的用户名参数
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

        UserDetailsService的几个实现类和接口:

                注:如果仅仅只引入Spring Security依赖,没有配置的情况下默认使用的用户是InMemoryUserDetailsManager提供的

                (1)UserDetailsManager接口: 在UserDetails的基础上,继续定义了添加、更新、删除用户、修改密码以及判断用户是否存在等五种方法

                (2)JdbcDaoImpl实现类:在UserDetails的基础上,通过spring-jdbc实现了从数据库中查询用户的方法

                (3)InMemoryUserDetailsManager实现类:实现了UserDetailsManager中关于用户的增删查改方法,不过都是基于内存的操作,数据并没有持久化

                (4)JdbcUserDetailsManager实现类:继承自JdbcDaoImpl同时又实现了UserDetailsMananger接口,可以实现对用户的增删查改操作,并且都会持久化到数据库中,但由于操作数据库的SQL都是提前写好的,不够灵活,因此实际开发中该类使用的并不多

                (5)CachingUserDetailsService实现类:特点是会将UserDetailsService缓存起来

                (6)UserDetailsServiceDelegator实现类:提供了UserDetailsService的懒加载功能

                (7)ReactiveUserDetailsServiceAdapter:webflux-web-security模块定义的UserDetailsService实现

        UserDetailsService的自动配置类——UserDetailsServiceAutoConfiguration类:

                什么情况下会进行自动配置?

                        (1)当前classpath下存在AuthenticationManager类

                        (2)当前项目中,系统没有提供AuthenticationManagerAuthenticationProviderUserDetailsService以及ClientRegistrationRepository实例

                自动配置提供的是InMemoryUserDetailsManager实例,而该实例中的用户数据源自SecurityProperties类的getUser()方法:

@ConfigurationProperties(prefix="spring.security")
public class SecurityProperties{

    //org.springframework.security.core.userdetails.User
    //这是Spring Security提供的一个实现了UserDetails接口的用户类
    private User user = new User();

    public User getUser(){
        return this.user;
    }

    public static class User{
        private String name = "user";
        private String password = UUID.randomUUID().toString();
        private List<String> roles = new ArrayList<>();
        //省略getter/setter
    }

}

                默认的用户密码在getOrDeducePassword方法中进行了二次处理,默认的encoder为null,所以密码的二次处理只是给密码加了一个前缀{noop},表示密码是明文存储的。

                在项目的配置文件中添加如下内容,便可以实现定制SecurityProperties.User中的属性值:

spring:
    security:
        user:
            name: lalala    # 默认的登录使用的用户名
            password: 123    # 密码
            roles: admin,user    # 权限

        默认页面生成:

                        上述例子中,存在着两个默认页面,及其对应的用于生成页面的过滤器:

                                登录页面:localhost:8080/login        ——>  DefaultLoginPageGeneratingFilter

                                注销页面:localhost:8080/logout    ——>  DefaultLogoutPageGeneratingFilter

                        DefaultLoginPageGeneratingFilter的执行流程:

                                (1)在doFilter()中判断,是否是以下三种情况之一:

                                                · 是登录的请求

                                                · 登录失败

                                                · 登出成功

                                (2)符合以上其中一种情况,则执行generateLoginPageHtml()构建登录页面

                                (3)将生成的登录页面前端代码通过HttpServletResponse返回给前端

登录表单配置:

        自定义配置:

                实现自定义配置主要是通过继承WebSecurityConfigurerAdapter来实现的:

                简化的配置示例(省略自定义登录页面和配置文件中定制的用户名密码):

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .anyRequest()
            .authenticated()
            .and()
            .formLogin()
            .loginPage("/login.html")
            .loginProcessingUrl("/doLogin")
            .defaultSuccessUrl("/index")
            .failureUrl("/login.html")
            .usernameParameter("uname")
            .passwordParameter("passwd")
            .permitAll()
            .and()
            .csrf()
            .disable();
    }

}

        其中defaultSuccessUrlsuccessForwardUrl区别如下:

                defaultSuccessUrl()是客户端跳转,而successForwardUrl()则是通过服务器端跳转来实现的,但最终所配置的都是AuthenticationSuccessHandler接口的实例

                服务端跳转的其中一个好处是可以携带登录的信息(成功、失败或者异常信息)

                (1)defaultSuccessUrl()方法表示当前用户登陆成功后,会自动重定向到登录前访问的地址上,如果用户登录前访问的就是登录页面,则登陆成功后会重定向到defaultSuccessUrl()方法参数中设置的页面。defaultSuccessUrl()有一个重载方法,第二个参数传true的话则效果与successForwardUrl()一致

                (2)successForwardUrl()方法则不会考虑用户之前的访问地址,只要用户登录成功,就会通过服务器端跳转到successForwardUrl()参数所指定的页面

        AuthenticationSuccessHandler接口:                

public interface AuthenticationSuccessHandler{
    default void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        FilterChain chain,
                                        Authentication authentication)
                                        throws IOException, ServletException{
        onAuthenticationSuccess(request, response, authentication);
        chain.doFilter(request, response);
    }
    
    void onAuthenticationSuccess(HttpServletRequest request,
                                HttpServletResponse response,
                                Authentication authentication)
                                throws IOException, ServletException;

}

                (1)第一个default方法: 在处理特定的认证请求Authentication Filter中会用到

                (2)第二个非default方法: 用来处理登录成功的具体事项,其中authentication参数保存了登录成功的用户信息

                该接口的三个实现类:

                        (1)SimpleUrlAuthenticationSuccessHandler: 继承自AbstractAuthenticationTargetUrlRequestHandler,通过其中的handle方法实现请求重定向

                        (2)SavedRequestAwareAuthenticationSuccessHandler:在SimpleUrlAuthenticationSuccessHandler的基础上增加了请求缓存的功能,可以记录之前请求的地址,进而在登陆成功后重定向到一开始访问的地址,使用defaultSuccessUrl()对应的实现类就是此类

                        (3)ForwardAuthenticationSuccessHandler:实现就是一个服务端跳转

                前后端分离的情况下,可以通过自定义AuthenticationSuccessHandler来返回登录成功的JSON数据。同样的,登录失败也可以自定义AuthenticationFailureHandler来返回登录失败的JSON数据。

        AuthenticationFailureHandler接口:

public interface AuthenticationFailureHandler{

    void onAuthenticationFailure(HttpServletRequest request,
                                HttpServletResponse response, 
                            AuthenticationException exception)
                            throws IOException, ServletException;    

}

                 该接口的五个实现类:

                        (1)SimpleUrlAuthenticationFailureHandler: 默认的处理逻辑是通过重定向跳转到登录页面,也可以通过配置forwardToDestination属性将重定向改为服务器端跳转,failureUrl方法的底层实现逻辑就是SimpleUrlAuthenticationFailureHandler。

                        (2)ExceptionMappingAuthenticationFailureHandler: 可以实现根据不同的异常类型,映射到不同的路径。

                        (3)ForwardAuthenticationFailureHandler:表示通过服务器端跳转来重新回到登录页面,failureForwardUrl方法的底层实现就是ForwardAuthenticationFailureHandler。

                        (4)AuthenticationEntryPointFailureHandler:Spring Security5.2新引进的处理类,可以通过AuthenticationEntryPoint来处理登录异常。

                        (5)DelegatingAuthenticationFailureHandler:可以实现为不同的异常类型配置不同的登录失败处理回调。

                        不使用failureForwardUrl方法,又想在登陆失败后通过服务端跳转回到登录页面,可以自定义SimpleUrlAuthenticationFailureHandler配置,并将forwardToDestination属性设置为true。

                自定义AuthenticationFailureHandler写法(一部分代码):

            .failureHandler((request,
                             response,
                             exception) ->{
                response.setContentType("application/json;charset=utf-8");
                Map<String,Object> resp = new HashMap<>();
                resp.put("status", 500);
                resp.put("msg", "登录失败!" + exception.getMessage());
                String msg = new ObjectMapper().writeValueAsString(resp);
                response.getWriter().write(msg);
            })

        注销登录:

                常用的配置:

                        (1)通过.logout()方法开启注销登录配置。

                        (2)logoutUrl指定了注销登录请求地址,默认是GET请求,路径为/logout

                        (3)invalidateHttpSession:表示是否使session失效,默认为true

                        (4)clearAuthentication:表示是否清除认证信息,默认为true

                        (5)logoutSuccessUrl:表示注销登录后的跳转地址。

                前后端分离,注销成功后就不需要页面跳转了,可以通过配置.logoutSuccessHandler(),自定义LogoutSuccessHandler来返回JSON数据,方式和上面AuthenticationFailureHandler的一致。通过配置.defaultLogoutSuccessHandlerFor()(可以配置多个),可以注册多个不同的注销成功回调函数,例:

            .defaultLogoutSuccessHandlerFor((req,resp,auth)->{
                resp.setContentType("application/json;charset=utf-8");
                Map<String,Object> result = new HashMap<>();
                result.put("status", 200);
                result.put("msg", "注销成功!");
                String msg = new ObjectMapper().writeValueAsString(resp);
                resp.getWriter().write(msg);
            }, new AntPathRequestMatcher("/logout", "GET"))
            .defaultLogoutSuccessHandlerFor((req,resp,auth)->{
                resp.setContentType("application/json;charset=utf-8");
                Map<String,Object> result = new HashMap<>();
                result.put("status", 200);
                result.put("msg", "注销成功!");
                String msg = new ObjectMapper().writeValueAsString(resp);
                resp.getWriter().write(msg);
            }, new AntPathRequestMatcher("/logout01", "GET"))

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值