SpringSecurity-Three

在SpringSecurity-Two中说到SpringSecurity中三个核心接口。并完成了一个小demo。但是在这个demo中没有完成用户登录逻辑。并且用的登录页面也是springsecurity默认的。这篇文章会完成自定义登录页面和登录返回信息。

1. 使用自己的定义的登录页面完成登录验证。我们的目的是,别人可以把我们这个项目拿出去直接当做一个安全框架使用,所以在配置登录页面这块就需要自己定义一个默认的页面。如果使用者需要使用自己默认的登录页面,只需要在application.yml文件中配置对应页面即可。

  • 在resources文件夹下创建一个html页面 image.png
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<h2>标准登录页面</h2>
<h3>表单登录</h3>
<form action="/authentication/form" method="post">
    <table>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="username"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td colspan="2"><button type="submit">登录</button></td>
        </tr>
    </table>
</form>
</body>
</html>

  • 在自定义SecurityConfig配置类中配置自定义html页面为登录页面。这里在loginPage()中配置一个url,通过这个url返回html页面,比固定写死返回一个页面要更好。
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()                                      //基于form表单的登录验证
                .loginPage("/authentication/require")                //指定登录页面
                .loginProcessingUrl("/authentication/form")   //表单的action路径
                .and()
                .authorizeRequests()        //关于请求的一些配置
		/**
                 * 这里需要注意一个错误,如果不适用antMatchers().permitAll()将登录的url设置为不需要身份验证的话
                 * 那么就会导致,请求重定向次数过多错误
                 */
                .antMatchers("/authentication/require",securityProperties.getBrowser().getLoginPage()).permitAll() //访问这个路径不需要身份验证
                .anyRequest()               //所有的请求
                .authenticated()            //身份验证
                .and()
                .csrf().disable();          //关闭跨站防护
    }
}

  • 我们需要在application.yml文件中添加如下配置,并且使其生效
zcf:
  security:
    browser:
      loginPage : /demo-signIn.html
  • 定义一个BrowserProperties类,在这个类中定义loginPage的值。
public class BrowserProperties {
    private String loginPage = "/zcf-signIn.html";
     /**
     * 赋予loginPage默认值,也就是如果用户不指定自己的登录页面,
     * 则使用我们提供的默认登录页面
     */
	
    public String getLoginPage() {
        return loginPage;
    }

    public void setLoginPage(String loginPage) {
        this.loginPage = loginPage;
    }
}
  • 定义一个SecurityProperties类,在这个类中配置zcf.security的值
    /**
     * 使用ConfigurationProperties配置一个zcf.security的属性
     */
@ConfigurationProperties(prefix = "zcf.security") 
public class SecurityProperties {
    /**
     * 提供BrowserProperties类获取的方法
     */
    private BrowserProperties browser = new BrowserProperties();
    

    public BrowserProperties getBrowser() {
        return browser;
    }

    public void setBrowser(BrowserProperties browser) {
        this.browser = browser;
    }
}

  • 然后只需要开启上边属性的配置即可
@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityCoreConfig {

}

  • 最后就是配置Controller了,这里的逻辑是,当用户访问.html页面的时候将自动跳转到登录页面,如果不是.html就提示相关信息。如图,当访问127.0.0.1:8081/user完成跳转,当访问index.html或者其他以.html结尾的url时跳转到登录页面
  • 当访问127.0.0.1:8081/user完成跳转 image.png
  • controller层代码
@RestController
public class BrowserSecurityController {
    private RequestCache requestCache = new HttpSessionRequestCache(); //请求缓存到session中

    private Logger logger = LoggerFactory.getLogger(getClass());

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); //完成请求跳转

    @Autowired
    private SecurityProperties securityProperties;
    /**
     * 当需要身份认证的时候,跳转到这里
     * @param request
     * @param response
     * @return
     */
    @RequestMapping("/authentication/require")
    @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
    public SimpleResponse requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
        SavedRequest savedRequest = requestCache.getRequest(request,response); //拿到请求
        if(savedRequest != null){
            String targetUrl = savedRequest.getRedirectUrl(); //拿到引发跳转的请求
            logger.info("引发跳转的请求是:"+targetUrl);
            //判断这个请求的url是不是以html结尾
            if(StringUtils.endsWithIgnoreCase(targetUrl,".html")){
                //跳转到指定的页面,如果用户有自定义的页面就跳转到自定义的页面,如果没有则跳转到我们自己自定义的页面
                redirectStrategy.sendRedirect(request,response,securityProperties.getBrowser().getLoginPage());
            }
        }
        return new SimpleResponse("访问的服务需要身份认证,请引导用户到登录页");
    }
}

2. 登录成功或失败有两种返回方式,一种是直接返回JSON信息,一种是重定向到指定页面。这里配置成,如果用户使用我们这个安全框架,他可以在application.yml文件中配置选择返回哪一种。这里从两个接口和它的实现类说起。

  • AuthenticationSuccessHandler接口,登录成功处理器。可以看到这个接口有只有一个方法,这个方法就是用来处理登录成功之后返回信息的,方法第三个参数Authentication也是一个接口,它主要存放用户的权限信息。
public interface AuthenticationSuccessHandler {
	void onAuthenticationSuccess(HttpServletRequest request,
			HttpServletResponse response, Authentication authentication)
			throws IOException, ServletException;

}

  • AuthenticationSuccessHandler接口有一个实现类,SimpleUrlAuthenticationSuccessHandler类,这个类中就是具体处理我们登录成功之后返回的信息,所以我们要自己写一个类继承这个类,覆盖父类中的方法,完成自定义返回类容。
@Component("zcfAuthenticationSuccessHandler")
public class ZcfAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private SecurityProperties securityProperties;
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        logger.info("登录成功");
        response.setContentType("application/json;charset=UTF-8");
        //将authentication转换成json格式的字符串返回
         response.getWriter().write(objectMapper.writeValueAsString(authentication));      
    }
}

  • 同理存在一个登录失败接口和其实现类,AuthenticationFailureHandler接口和其实现类SimpleUrlAuthenticationFailureHandler,这里逻辑和登录成功一样。
/**
 * AuthenticationFailureHandler失败处理器接口
 */
@Component("zcfAuthenticationFailureHandler")
public class ZcfAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private SecurityProperties securityProperties;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        /**
         * 与登录成功处理器接口不同的是,方法第三个参数AuthenticationException不是Authentication
         * 因为登录失败时没法拿到用户的信息的
         */
        logger.info("登录失败");
        
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            response.setContentType("application/json;charset=UTF-8");
            //将authentication转换成json格式的字符串返回
            response.getWriter().write(objectMapper.writeValueAsString(exception));       
    }
}

  • 编写完自己的登录成功和登录失败处理器之后,需要让SpringSecurity使用他们
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 导入自己写的AuthenticationSuccessHandler的实现类
     */
    @Autowired
    private AuthenticationSuccessHandler zcfAuthenticationSuccessHandler;

    /**
     * 导入自己写的AuthenticationFailureHandler的实现类
     */
    @Autowired
    private AuthenticationFailureHandler zcfAuthenticationFailureHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()                                      //基于form表单的登录验证
                .loginPage("/authentication/require")                //指定登录页面
                .loginProcessingUrl("/authentication/form")   //表单的action路径
                .successHandler(zcfAuthenticationSuccessHandler)//配置自己写的登录成功处理器
                .failureHandler(zcfAuthenticationFailureHandler)
                .and()
                .authorizeRequests()        //关于请求的一些配置
                /**
                 * 这里需要注意一个错误,如果不适用antMatchers().permitAll()将登录的url设置为不需要身份验证的话
                 * 那么就会导致,请求重定向次数过多错误
                 */
                .antMatchers("/authentication/require",securityProperties.getBrowser().getLoginPage()).permitAll() //访问这个路径不需要身份验证
                .anyRequest()               //所有的请求
                .authenticated()            //身份验证
                .and()
                .csrf().disable();          //关闭跨站防护
    }
}

  • 访问项目路径,当登录成功之后在返回的JSON数据中可以看到Authentication接口存放的用户权限信息 image.png

3. 在上边我们说到,要完成自定义的,当用户使用这个框架的时候,可以根据用户的配置来选择返回什么信息。

  • 定义一个枚举类型
public enum LoginType {
    REDIRECT, //登录之后重定向到指定页面
    JSON      //登录之后返回JSON数据
}

  • 在我前边写过的BrowserProperties类中,添加枚举
public class BrowserProperties {
    /**
     * 赋予loginPage默认值,也就是如果用户不指定自己的登录页面,
     * 则使用我们提供的默认登录页面
     */
    private String loginPage = "/zcf-signIn.html";

    /**
     * 配置登录之后是跳转还是返回json
     * @return
     */
    private LoginType loginType = LoginType.JSON;

    public String getLoginPage() {
        return loginPage;
    }

    public void setLoginPage(String loginPage) {
        this.loginPage = loginPage;
    }

    public LoginType getLoginType() {
        return loginType;
    }

    public void setLoginType(LoginType loginType) {
        this.loginType = loginType;
    }
}

  • 最后就是修改一下自定义登录成功和失败处理器的代码,在其中加入一个判断
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        logger.info("登录成功");
        if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
            response.setContentType("application/json;charset=UTF-8");
            //将authentication转换成json格式的字符串返回
            response.getWriter().write(objectMapper.writeValueAsString(authentication));
        }else {
            super.onAuthenticationSuccess(request,response,authentication);
        }
    }

  • 修改登录失败自定义处理器代码
 public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        /**
         * 与登录成功处理器接口不同的是,方法第三个参数AuthenticationException不是Authentication
         * 因为登录失败时没法拿到用户的信息的
         */
        logger.info("登录失败");
        if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            response.setContentType("application/json;charset=UTF-8");
            //将authentication转换成json格式的字符串返回
            response.getWriter().write(objectMapper.writeValueAsString(exception));

        }else{
            super.onAuthenticationFailure(request,response,exception);

        }

    }

以上就简单的完成了自定义登录页面,所有的项目源码都在github中,在SpringSecurity-two中有链接

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×