关于 Spring Security 的学习已经告一段落了,刚开始接触该安全框架感受很迷茫,总以为没有 Shiro 灵活,到后来的深刻学习和探究才发现它很是强大。简单快速集成,基本不用写任何代码,拓展起来也很是灵活和强大。
集成完该框架默认状况下,系统帮咱们生成一个登录页,默认除了登录其余请求都须要进行身份认证,没有身份认证前的任何操做都会跳转到默认登陆页。
默认生成的密码也会在控制台输出。html
接下来咱们可能须要本身控制一下权限,自定义一下登陆界面前端
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .formLogin() .loginPage("/login.html") //自定义登陆界面 .loginProcessingUrl("/login.action") //指定提交地址 .defaultSuccessUrl("/main.html") //指定认证成功跳转界面 //.failureForwardUrl("/error.html") //指定认证失败跳转界面(注: 转发须要与提交登陆请求方式一致) .failureUrl("/error.html") //指定认证失败跳转界面(注: 重定向须要对应的方法为 GET 方式) .usernameParameter("username") //username .passwordParameter("password") //password .permitAll() .and() .logout() .logoutUrl("/logout.action") //指定登出的url, controller里面不用写对应的方法 .logoutSuccessUrl("/login.html") //登出成功跳转的界面 .permitAll() .and() .authorizeRequests() .antMatchers("/register*").permitAll() //设置不须要认证的 .mvcMatchers("/main.html").hasAnyRole("admin") .anyRequest().authenticated() //其余的所有须要认证 .and() .exceptionHandling() .accessDeniedPage("/error.html"); //配置权限失败跳转界面 (注: url配置不会被springmvc异常处理拦截, 可是注解配置springmvc异常机制能够拦截到) }
从上面配置能够看出自定义配置能够简单地分为四个模块(登陆页面自定义、登出自定义、权限指定、异常设定),每一个模块都对应着一个过滤器,详情请看 Spring Security 进阶-原理篇spring
须要注意的是:数据库
loginProcessingUrl(..)
、登出URL logoutUrl(..)
都是对应拦截器的匹配地址,会在对应的过滤器里面执行相应的逻辑,不会执行到 Controller 里面的方法。defaultSuccessUrl(..)
、登陆认证失败跳转的URL failureUrl(..)
、登陆认证失败转发的URL failureForwardUrl(..)
......以及下面登出和权限配置的URL 能够是静态界面地址,也能够是 Controller 里面对应的方法。WebAttributes.AUTHENTICATION_EXCEPTION
来取出,可是前提是使用系统提供的身份认证异常处理handler SimpleUrlAuthenticationFailureHandler
。AccessDeniedHandlerImpl
。大多数开发状况下都是先后端分离,响应也都是异步的,不是上面那种表单界面的响应方式,虽然经过上面跳转到URL对应的 Controller 里面的方法也能解决,可是大多数状况下咱们须要的是极度简化,这时候一些自定义的处理 handler 就油然而生。segmentfault
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .formLogin() .loginProcessingUrl("/login") .successHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println("****** 身份认证成功 ******"); response.setStatus(HttpStatus.OK.value()); } }) .failureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { System.out.println("****** 身份认证失败 ******"); response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } }) .permitAll() .and() .logout() .logoutUrl("/logout") .logoutSuccessHandler(new LogoutSuccessHandler() { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println("****** 登出成功 ******"); response.setStatus(HttpStatus.OK.value()); } }) .permitAll() .and() .authorizeRequests() .antMatchers("/main").hasAnyRole("admin") .and() .exceptionHandling() .authenticationEntryPoint(new AuthenticationEntryPoint() { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { System.out.println("****** 没有进行身份认证 ******"); response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } }) .accessDeniedHandler(new AccessDeniedHandler() { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { System.out.println("****** 没有权限 ******"); response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } }); }
注意:后端
logoutSuccessHandler(..)
,登陆身份认证失败的 handler failureHandler(..)
,以避免默认这样两个步骤向不存在的登陆页跳转。failureHandler(..)
和 没有进行身份认证的异常 handler authenticationEntryPoint(..)
,这两个有区别,前者是在认证过程当中出现异常处理,后者是在访问须要进行身份认证的URL时没有进行身份认证异常处理。开发的时候咱们须要本身来实现登陆登出的流程,下面来个最简单的自定义。安全
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .logout() .logoutUrl("/logout") .logoutSuccessHandler(new LogoutSuccessHandler() { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println("****** 登出成功 ******"); response.setStatus(HttpStatus.OK.value()); } }) .permitAll() .and() .authorizeRequests() .antMatchers("/main").hasAnyRole("admin") .and() .exceptionHandling() .authenticationEntryPoint(new AuthenticationEntryPoint() { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { System.out.println("****** 没有进行身份认证 ******"); response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } }) .accessDeniedHandler(new AccessDeniedHandler() { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { System.out.println("****** 没有权限 ******"); response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } }) .and() .addFilterBefore(new LoginFilter(), UsernamePasswordAuthenticationFilter.class); }
注意:session
SecurityContextHolder.clearContext();
,可是建议配置,通常登陆和登出最好都在过滤器里面进行处理。自定义登陆过滤器详情mvc
public class LoginFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; if ("/login".equals(httpServletRequest.getServletPath())) { //开始登陆过程 String username = httpServletRequest.getParameter("username"); String password = httpServletRequest.getParameter("password"); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, password); //模拟数据库查出来的 User.UserBuilder userBuilder = User.withUsername(username); userBuilder.password("123"); userBuilder.roles("user", "admin"); UserDetails user = userBuilder.build(); if (user == null) { System.out.println("****** 自定义登陆过滤器 该用户不存在 ******"); httpServletResponse.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } if (!user.getUsername().equals(authentication.getPrincipal())) { System.out.println("****** 自定义登陆过滤器 帐号有问题 ******"); httpServletResponse.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } if (!user.getPassword().equals(authentication.getCredentials())) { System.out.println("****** 自定义登陆过滤器 密码有问题 ******"); httpServletResponse.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user.getUsername(), authentication.getCredentials(), user.getAuthorities()); result.setDetails(authentication.getDetails()); //注: 最重要的一步 SecurityContextHolder.getContext().setAuthentication(result); httpServletResponse.setStatus(HttpStatus.OK.value()); } else { chain.doFilter(request, response); } } }
注意:框架