自定义表单登录页面
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() //表单登陆认证方法,浏览器跳转到specurity默认登陆页面 //http.httpBasic()//浏览器弹出登陆对话框登陆认证方式 .loginPage("/login.html")//自定义登陆页面 .loginProcessingUrl("/user/login")//用户登陆表单提交接口,默认/login .and() .authorizeRequests() ////设置请求符合访问资源的权限 .antMatchers("/login.html","/error.html")//匹配器 .permitAll()//访问这两个页面不需要认证 .anyRequest().authenticated(); //对任何请求都要登陆认证后才能访问 } @Bean public PasswordEncoder passwordEncoder() { //return NoOpPasswordEncoder.getInstance(); //已弃用 return new BCryptPasswordEncoder(); } }
页面放入resources/static
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/login" method="post"> 用户名: <input type="text" name="username123" /> <br/> 密码:<input type="password" name="password123" /> <br/> <input type="submit" value="登陆"/> </form> </body> </html>
表单登陆的接口默认是/login,是由UsernamePasswordAuthenticationFilter过滤器的无参构造函数处理的
框架源码
public UsernamePasswordAuthenticationFilter() { super(new AntPathRequestMatcher("/login", "POST")); }
.loginProcessingUrl("/user/login")//用户登陆表单提交接口
改变用户登陆提交接口(不需要定义接口),由UsernamePasswordAuthenticationFilter过滤器的attemptAuthentication方法处理.
实现UsernamePasswordAuthenticationFilter的attemptAuthentication方法可自定义逻辑
框架源码
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (this.postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } else { String username = this.obtainUsername(request); String password = this.obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); this.setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } }
自定义身份认证跳转
自定义表单登陆页面是一种统一的身份认证方式,有的场景不需要返回这种html的登陆页面,需要返回json,包含状态码和错误信息
1.请求的身份认证由security判断,需要认证跳转到自定义的Controller方法上,这个方法url在配置类configure方法中http.loginPage("/authentication/require")配置(登陆静态页改为url)
2.在跳转到自定义Controller方法之前,security会将这次请求缓存在HttpSeesionRequestCache类中,自定义Controller类可以设置这个类为成员属性拿到这次请求的缓存
3.跳转登陆页面是通过配置选择使用该模块的统一登陆页面,还是使用该模块的模块自定义登陆页面
4.请求url是非.html,则返回json信息
borowser模块
import com.wzl.properties.SecurityProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter { //security配置类 @Autowired private SecurityProperties securityProperties; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() //表单登陆认证方法,浏览器跳转到specurity默认登陆页面 //http.httpBasic()//浏览器弹出登陆对话框登陆认证方式 //.loginPage("/login.html")//自定义登陆页面,自定义登陆url,security判断请求是否需要登陆认证,如果需要跳转该登陆页面 .loginPage("/authentication/require")//自定义登陆url,security判断请求是否需要登陆认证,如果需要跳转该url .loginProcessingUrl("/user/login")//用户登陆页面提交接口 .and() .authorizeRequests() ////设置请求符合访问资源的权限 .antMatchers(securityProperties.getBrowser().getLoginPage(),"/login.html","/error.html","/authentication/require")//匹配器 .permitAll()//匹配器匹配的请求不需要认证 .anyRequest().authenticated() //对任何请求都要登陆认证后才能访问 .and() .csrf().disable();//关闭跨域伪造关闭 } @Bean public PasswordEncoder passwordEncoder() { //return NoOpPasswordEncoder.getInstance(); //已弃用 return new BCryptPasswordEncoder(); } }
.loginPage("/authentication/require") 跳转的控制器方法
import com.wzl.properties.SecurityProperties; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.web.DefaultRedirectStrategy; import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.savedrequest.HttpSessionRequestCache; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.savedrequest.SavedRequest; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.Map; @Slf4j @RestController public class BrowserSecurityController { //security缓存了需要身份认证的请求 private RequestCache requestCache = new HttpSessionRequestCache(); //跳转工具 private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); //security配置类 @Autowired private SecurityProperties securityProperties; /** .loginPage("/authentication/require") * 当身份认证时,跳转这里 * @param request * @param response * @return */ @GetMapping("/authentication/require") @ResponseStatus(code = HttpStatus.UNAUTHORIZED) //401 未授权状态码 public ResponseEntity<Map<String,Object>> requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException { HashMap<String, Object> map = new HashMap<>(); //从缓存中获取这次请求 SavedRequest savedRequest = requestCache.getRequest(request, response); if (savedRequest!=null) { String url = savedRequest.getRedirectUrl(); log.info("引发跳转的请求:"+url); //请求url是.html,跳转到登陆页 if (StringUtils.endsWithIgnoreCase(url, ".html")) { //跳转的页面可以是统一的自定义登陆页面,也可以是使用该模块的模块自定义登陆面 redirectStrategy.sendRedirect(request,response,securityProperties.getBrowser().getLoginPage()); } } //返回需要身份认证的提示信息 map.put("msg", "访问的服务需要身份认证,请引导用户到登录页"); return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(map); } }
配置项封装
- 不使用统一自定义登陆页面,通过配置当前模块自定义登陆页面
# security配置 wzl: security: browser: loginPage: "/myLogin.html" #当前模块resources/static/
Core模块
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.boot.context.properties.ConfigurationProperties; @Data @NoArgsConstructor @AllArgsConstructor @ConfigurationProperties(prefix = "wzl.security") public class SecurityProperties { private BrowserProperties browser = new BrowserProperties(); }
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class BrowserProperties { private String loginPage = "/login.html"; }
import com.wzl.properties.SecurityProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; @Configuration @EnableConfigurationProperties(SecurityProperties.class) public class SecurityCoreConfig { }
自定义登录成功处理
登陆成功,用户积分增加等业务。
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private String url; public MyAuthenticationSuccessHandler(String url){ this.url = url; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { User user = (User) authentication.getPrincipal(); System.out.println(user.getPassword());//输出null,保护密码 System.out.println(user.getUsername()); System.out.println(user.getRoles()); response.sendRedirect(url); } }
//登陆成功,是否指定跳转到首页
//.defaultSuccessUrl("/main.html",true)
//.defaultSuccessUrl("/toMain",true)
.successHandler(new MyAuthenticationSuccessHandler("http://www.baidu.com"))
自定义登陆失败处理
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { private String url; public MyAuthenticationFailureHandler(String url){ this.url = url; } @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { response.sendRedirect(url); } }
//.failureUrl("/error.html")
.failureHandler(new MyAuthenticationFailureHandler("http://www.baidu.com"))