Spring Security学习(2) - 自定义登录

自定义登录页

替换默认登陆页

将自定义的登录页放到resources目录下,然后在WebSecurityConfigurerAdapter的实现类重写configure方法。

   @Override
   public void configure(WebSecurity web) throws Exception {
       web.ignoring().antMatchers("/js/**", "/css/**", "/images/**"); // 静态资源放行,不进行权限控制
   }

   @Override
   protected void configure(HttpSecurity http) throws Exception {
       http.formLogin() // 表单方式认证
               // .httpBasic() // HttpBasic方式认证
               .loginPage("/login.html") // 登陆页面
               .loginProcessingUrl("/login") // 登陆鉴权地址,这里的login为SpringSecurity默认的鉴权地址
               .and()
               .authorizeRequests() // 授权配置
               .antMatchers("/login.html").permitAll() // 不用鉴权的资源
               .anyRequest()  // 所有请求
               .authenticated() // 都需要认证
               .and().csrf().disable(); // 关闭CSRF攻击防御
   }

根据访问资源类型进行差异化处理

处理效果:在未登录的情况下,访问html资源的时候跳转到登录页,访问其他类型资源返回JSON格式错误信息,状态码为401。
首先在WebSecurityConfigurerAdapter的实现类重写configure方法。

    @Override
	protected void configure(HttpSecurity http) throws Exception {
	    http.formLogin() // 表单方式认证
	            // .httpBasic() // HttpBasic方式认证
	            .loginPage("/authentication/require") // 登录跳转URL
	            .loginProcessingUrl("/login") // 登陆鉴权地址,这里的login为SpringSecurity默认的鉴权地址
	            .and()
	            .authorizeRequests() // 授权配置
	            .antMatchers("/authentication/require", "/login.html").permitAll() // 不用鉴权的资源
	            .anyRequest()  // 所有请求
	            .authenticated() // 都需要认证
	            .and().csrf().disable() // 关闭CSRF攻击防御
	    ;
	}

然后定义一个控制器BrowserSecurityController,处理这个请求:

@RestController
public class BrowserSecurityController {
    private RequestCache requestCache = new HttpSessionRequestCache();
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @GetMapping("/authentication/require")
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ResponseBody
    public String requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
        SavedRequest savedRequest = requestCache.getRequest(request, response);
        if (savedRequest != null) {
            String targetUrl = savedRequest.getRedirectUrl();
            if (StringUtils.endsWithIgnoreCase(targetUrl, ".html"))
                redirectStrategy.sendRedirect(request, response, "/login.html");
        }
        return "{\"errMsg\":\"访问的资源需要身份认证!\"}";
    }
}

其中HttpSessionRequestCache为Spring Security提供的用于缓存请求的对象,通过调用它的getRequest方法可以获取到本次请求的HTTP信息。DefaultRedirectStrategy的sendRedirect为Spring Security提供的用于处理重定向的方法。

上面代码获取了引发跳转的请求,根据请求是否以.html为结尾来对应不同的处理方法。如果是以.html结尾,那么重定向到登录页面,否则返回”访问的资源需要身份认证!”信息,并且HTTP状态码为401(HttpStatus.UNAUTHORIZED)。

这样当我们访问http://localhost:8080/hello的时候页面便会跳转到http://localhost:8080/authentication/require,并且输出:{“errMsg”:“访问的资源需要身份认证!”},当我们访问http://localhost:8080/hello.html的时候,页面将会跳转到登录页面。

处理成功和失败

Spring Security有一套默认的处理登录成功和失败的方法:当用户登录成功时,页面会跳转到登录页面并引发登陆操作,比如在未登录的情况下访问http://localhost:8080/hello,页面会跳转到登录页,登录成功后再跳转回来;登录失败时则是跳转到Spring Security默认的错误提示页面。下面我们通过一些自定义配置来替换这套默认的处理机制。

自定义登录成功逻辑

要改变默认的处理成功逻辑很简单,只需要实现org.springframework.security.web.authentication.AuthenticationSuccessHandler接口的onAuthenticationSuccess方法即可:

@Component
public class CustAuthenticationSucessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(new ObjectMapper().writeValueAsString(authentication));
    }
}

其中Authentication参数既包含了认证请求的一些信息,比如IP,请求的SessionId等,也包含了用户信息,即前面提到的User对象。通过上面这个配置,用户登录成功后页面将打印出Authentication对象的信息。
要使这个配置生效,还要在WebSecurityConfigurerAdapter实现类的configure方法中进行配置:

@Autowired
private CustAuthenticationSucessHandler authenticationSucessHandler;

   @Override
   protected void configure(HttpSecurity http) throws Exception {
       http.formLogin() // 表单方式认证
               // .httpBasic() // HttpBasic方式认证
               .loginPage("/authentication/require") // 登录跳转URL
               .loginProcessingUrl("/login") // 登陆鉴权地址,这里的login为SpringSecurity默认的鉴权地址
               .successHandler(authenticationSucessHandler) // 处理登录成功
               .and()
               .authorizeRequests() // 授权配置
               .antMatchers("/authentication/require", "/login.html").permitAll() // 不用鉴权的资源
               .anyRequest()  // 所有请求
               .authenticated() // 都需要认证
               .and().csrf().disable() // 关闭CSRF攻击防御
       ;
   }

此时重启项目登录后页面将会输出Authentication对象的JSON信息。

除此之外,我们也可以在登录成功后做页面的跳转,修改MyAuthenticationSucessHandler:

@Component
public class CustAuthenticationSucessHandler implements AuthenticationSuccessHandler {

    private RequestCache requestCache = new HttpSessionRequestCache();
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        SavedRequest savedRequest = requestCache.getRequest(request, response);
        redirectStrategy.sendRedirect(request, response, savedRequest.getRedirectUrl());
    }
}

通过上面配置,登录成功后页面将跳转回引发跳转的页面。如果想指定跳转的页面,比如跳转到/test/index,可以将savedRequest.getRedirectUrl()修改为/test/index:

@Component
public class CustAuthenticationSucessHandler implements AuthenticationSuccessHandler {
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException {
        redirectStrategy.sendRedirect(request, response, "/test/index");
    }
}

然后在TestController中定义一个处理该请求的方法:

@RestController
@RequestMapping("test")
public class TestController {
    @GetMapping("index")
    public Object index(){
        return SecurityContextHolder.getContext().getAuthentication();
    }
}

登录成功后,便可以使用SecurityContextHolder.getContext().getAuthentication()获取到Authentication对象信息。除了通过这种方式获取Authentication对象信息外,也可以使用下面这种方式:

@RestController
@RequestMapping("test")
public class TestController {
    @GetMapping("index")
    public Object index(Authentication authentication) {
        return authentication;
    }
}

重启项目,登录成功后,页面将跳转到http://localhost:8080/index。

自定义登录失败逻辑

和自定义登录成功处理逻辑类似,自定义登录失败处理逻辑需要实现org.springframework.security.web.authentication.AuthenticationFailureHandler的onAuthenticationFailure方法:

@Component
public class CustAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException {
    }
}

onAuthenticationFailure方法的AuthenticationException参数是一个抽象类,Spring Security根据登录失败的原因封装了许多对应的实现类,不同的失败原因对应不同的异常,比如用户名或密码错误对应的是BadCredentialsException,用户不存在对应的是UsernameNotFoundException,用户被锁定对应的是LockedException等。
如果我们需要在登录失败的时候返回失败信息,可以这样处理:

@Component
public class CustAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException {
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(new ObjectMapper().writeValueAsString(exception.getMessage()));
    }
}

状态码定义为500(HttpStatus.INTERNAL_SERVER_ERROR.value()),即系统内部异常。
同样,我们需要在WebSecurityConfigurerAdapter实现类的configure方法中进行配置:

@Autowired
private CustAuthenticationFailureHandler authenticationFailureHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin() // 表单方式认证
            // .httpBasic() // HttpBasic方式认证
            .loginPage("/authentication/require") // 登录跳转URL
            .loginProcessingUrl("/login") // 登陆鉴权地址,这里的login为SpringSecurity默认的鉴权地址
            .successHandler(authenticationSucessHandler) // 处理登录成功
            .failureHandler(authenticationFailureHandler) // 处理登录失败
            .and()
            .authorizeRequests() // 授权配置
            .antMatchers("/authentication/require", "/login.html").permitAll() // 不用鉴权的资源
            .anyRequest()  // 所有请求
            .authenticated() // 都需要认证
            .and().csrf().disable() // 关闭CSRF攻击防御
    ;
}

自定义用户认证

自定义认证的过程需要实现Spring Security提供的UserDetailService接口,该接口只有一个抽象方法loadUserByUsername,源码如下:

public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

loadUserByUsername方法返回一个UserDetail对象,该对象也是一个接口,包含一些用于描述用户信息的方法,主要方法的含义如下:

  • getAuthorities:获取用户包含的权限,返回权限集合,权限是一个继承了GrantedAuthority的对象;
  • getPassword和getUsername方法:用于获取密码和用户名;
  • isAccountNonExpired方法:返回boolean类型,用于判断账户是否未过期,未过期返回true反之返回false;
  • isAccountNonLocked方法:用于判断账户是否未锁定;
  • isCredentialsNonExpired方法:用于判断用户凭证是否没过期,即密码是否未过期;
  • isEnabled方法:用于判断用户是否可用。

实现(使用任意用户名及密码admin登陆)

  1. 创建UserDetailsService的实现类,自定义密码处理逻辑:

    @Configuration
    public class CustUserDetailServiceImpl implements UserDetailsService {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // 模拟密码获取逻辑
            String password = this.passwordEncoder.encode("admin");
            // 输出加密后的密码
            System.out.println(password);
            return new User(username, password, true,
                    true, true,
                    true, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
        }
    }
    
  2. 创建PasswordEncoder对象
    CustUserDetailServiceImpl还注入了PasswordEncoder对象,该对象用于密码加密,注入前需要手动配置。添加PasswordEncoder的实例:

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值