- 引入依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
- 自定义认证过程 自定义认证的过程须要实现Spring Security提供的UserDetailService接口 ,源码以下:
public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }
loadUserByUsername方法返回一个UserDetail对象,该对象也是一个接口,包含一些用于描述用户信息的方法,源码以下:css
public interface UserDetails extends Serializable { // 获取用户包含的权限,返回权限集合,权限是一个继承了GrantedAuthority的对象; Collection<? extends GrantedAuthority> getAuthorities(); // 获取密码 String getPassword(); // 获取帐号/用户名 String getUsername(); // 帐户是否过时 boolean isAccountNonExpired(); //帐户是否被锁定 boolean isAccountNonLocked(); //用户凭证是否过时 boolean isCredentialsNonExpired(); //用户是否可用 boolean isEnabled(); }
- 建立实现自定义认证接口的类:
@Configuration public class UserDetailService implements UserDetailsService { @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 模拟一个用户,实际项目中应为: 根据用户名查找数据库,若是没有记录则会返回null,有则返回UserDetails对象 MyUser user = new MyUser(); user.setUserName(username); user.setPassword(this.passwordEncoder.encode("123456")); // 输出加密后的密码 System.out.println(user.getPassword()); // 返回对象以后 会在内部进行认证(密码/盐/加密过密码等) return new User(username, user.getPassword(), user.isEnabled(), user.isAccountNonExpired(), user.isCredentialsNonExpired(), user.isAccountNonLocked(), AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); } }
- 建立用户类
@Data public class MyUser implements Serializable { private static final long serialVersionUID = 3497935890426858541L; private String userName; private String password; private boolean accountNonExpired = true; private boolean accountNonLocked= true; private boolean credentialsNonExpired= true; private boolean enabled= true; }
- 配置类:
@Configuration public class MySecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } ... } 注:PasswordEncoder是一个密码加密接口,而BCryptPasswordEncoder是Spring Security提供的一个实现方法,咱们也能够本身实现PasswordEncoder。 不过Spring Security实现的BCryptPasswordEncoder已经足够强大,它对相同的密码进行加密后能够生成不一样的结果
启动项目:访问http://localhost:8080/login, 即可以使用任意用户名以及123456做为密码登陆系统html
BCryptPasswordEncoder对相同的密码生成的结果每次都是不同的git
- 替换默认登陆页 直接在src/main/resources/resources目录下定义一个login.html(不须要Controller跳转)
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>登陆</title> <link rel="stylesheet" href="css/login.css" type="text/css"> </head> <body> <form class="login-page" action="/login" method="post"> <div class="form"> <h3>帐户登陆</h3> <input type="text" placeholder="用户名" name="username" required="required" /> <input type="password" placeholder="密码" name="password" required="required" /> <button type="submit">登陆</button> </div> </form> </body> </html>
在MySecurityConfig中添加:github
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 表单登陆 // http.httpBasic() // HTTP Basic .loginPage("/login.html") //指定了跳转到登陆页面的请求URL .loginProcessingUrl("/login") //对应登陆页面form表单的action="/login" .and() .authorizeRequests() // 受权配置 //.antMatchers("/login.html").permitAll()表示跳转到登陆页面的请求不被拦截,不然会进入无限循环 .antMatchers("/login.html").permitAll() .anyRequest() // 全部请求 .authenticated()// 都须要认证 .and().csrf().disable(); // 关闭csrf防护 }
访问http://localhost:8080/hello ,会看到页面已经被重定向到了http://localhost:8080/login.html 使用任意用户名+密码123456登陆web
在未登陆的状况下,当用户访问html资源的时候,若是已经登录则返回JSON数据,不然直接跳转到登陆页,状态码为401。spring
要实现这个功能咱们将loginPage的URL改成/authentication/require,而且在antMatchers方法中加入该URL,让其免拦截:数据库
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 表单登陆 // http.httpBasic() // HTTP Basic .loginPage("/authentication/require") // 登陆跳转 URL .loginProcessingUrl("/login") // 处理表单登陆 URL .and() .authorizeRequests() // 受权配置 .antMatchers("/authentication/require", "/login.html").permitAll() // 登陆跳转 URL 无需认证 .anyRequest() // 全部请求 .authenticated() // 都须要认证 .and().csrf().disable(); }
建立控制器MySecurityController,处理这个请求:json
@RestController public class MySecurityController { //RequestCache requestCache是Spring Security提供的用于缓存请求的对象 private RequestCache requestCache = new HttpSessionRequestCache(); //DefaultRedirectStrategy是Spring Security提供的重定向策略 private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); @GetMapping("/authentication/require") @ResponseStatus(HttpStatus.UNAUTHORIZED) //HttpStatus.UNAUTHORIZED 未认证 状态码401 public String requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException { //getRequest方法能够获取到本次请求的HTTP信息 SavedRequest savedRequest = requestCache.getRequest(request, response); if (savedRequest != null) { String targetUrl = savedRequest.getRedirectUrl(); if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) //sendRedirect为Spring Security提供的用于处理重定向的方法 redirectStrategy.sendRedirect(request, response, "/login.html"); } return "访问的资源须要身份认证!"; } }
上面代码获取了引起跳转的请求,根据请求是否以.html为结尾来对应不一样的处理方法。若是是以.html结尾,那么重定向到登陆页面,不然返回”访问的资源须要身份认证!”信息,而且HTTP状态码为401(HttpStatus.UNAUTHORIZED)。缓存
这样当咱们访问http://localhost:8080/hello 的时候页面便会跳转到http://localhost:8080/authentication/require,springboot
,
当咱们访问http://localhost:8080/hello.html 的时候,页面将会跳转到登陆页面。
- 处理成功和失败 Spring Security有一套默认的处理登陆成功和失败的方法:当用户登陆成功时,页面会跳转会引起登陆的请求,好比在未登陆的状况下访问http://localhost:8080/hello, 页面会跳转到登陆页,登陆成功后再跳转回来;登陆失败时则是跳转到Spring Security默认的错误提示页面。下面 经过一些自定义配置来替换这套默认的处理机制。
自定义登陆成功逻辑 要改变默认的处理成功逻辑很简单,只须要实现org.springframework.security.web.authentication.AuthenticationSuccessHandler接口的onAuthenticationSuccess方法便可:
@Component public class MyAuthenticationSucessHandler implements AuthenticationSuccessHandler { @Autowired private ObjectMapper mapper; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { response.setContentType("application/json;charset=utf-8"); // 将认证信息转换成jsonString写入response response.getWriter().write(mapper.writeValueAsString(authentication)); } }
其中Authentication参数既包含了认证请求的一些信息,好比IP,请求的SessionId等,也包含了用户信息,即前面提到的User对象。经过上面这个配置,用户登陆成功后页面将打印出Authentication对象的信息。
要使这个配置生效,咱们还在MySecurityConfig的configure中配置它:
@Configuration public class MySecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyAuthenticationSucessHandler authenticationSucessHandler; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 表单登陆 // http.httpBasic() // HTTP Basic .loginPage("/authentication/require") // 登陆跳转 URL .loginProcessingUrl("/login") // 处理表单登陆 URL .successHandler(authenticationSucessHandler) // 处理登陆成功 .and() .authorizeRequests() // 受权配置 .antMatchers("/authentication/require", "/login.html").permitAll() // 登陆跳转 URL 无需认证 .anyRequest() // 全部请求 .authenticated() // 都须要认证 .and().csrf().disable(); } }
咱们将MyAuthenticationSucessHandler注入进来,并经过successHandler方法进行配置。
这时候重启项目登陆后页面将会输出以下JSON信息:
像password,credentials这些敏感信息,Spring Security已经将其屏蔽。
除此以外,咱们也能够在登陆成功后作页面的跳转,修改MyAuthenticationSucessHandler:
@Component public class MyAuthenticationSucessHandler implements AuthenticationSuccessHandler { private RequestCache requestCache = new HttpSessionRequestCache(); private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { SavedRequest savedRequest = requestCache.getRequest(request, response); redirectStrategy.sendRedirect(request, response, savedRequest.getRedirectUrl()); } }
经过上面配置,登陆成功后页面将跳转回引起跳转的页面。若是想指定跳转的页面,好比跳转到/index,能够将savedRequest.getRedirectUrl()修改成/index:
@Component public class MyAuthenticationSucessHandler implements AuthenticationSuccessHandler { private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { redirectStrategy.sendRedirect(request, response, "/index"); } }
在IndexController中定义一个处理该请求的方法:
@RestController public class IndexController { @GetMapping("index") public Object index(){ return SecurityContextHolder.getContext().getAuthentication(); } }
登陆成功后,即可以使用SecurityContextHolder.getContext().getAuthentication()获取到Authentication对象信息。除了经过这种方式获取Authentication对象信息外,也能够使用下面这种方式:
@RestController public class IndexController { @GetMapping("index") public Object index(Authentication authentication) { return authentication; } }
重启项目,登陆成功后,页面将跳转到http://localhost:8080/index:
- 自定义登陆失败逻辑 和自定义登陆成功处理逻辑相似,自定义登陆失败处理逻辑须要实现org.springframework.security.web.authentication.AuthenticationFailureHandler的onAuthenticationFailure方法:
@Component public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { } }
onAuthenticationFailure方法的AuthenticationException参数是一个抽象类,Spring Security根据登陆失败的缘由封装了许多对应的实现类,
不一样的失败缘由对应不一样的异常,好比用户名或密码错误对应的是BadCredentialsException,用户不存在对应的是UsernameNotFoundException,用户被锁定对应的是LockedException等。
假如咱们须要在登陆失败的时候返回失败信息,能够这样处理:
@Component public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { @Autowired private ObjectMapper mapper; @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(mapper.writeValueAsString(exception.getMessage())); } }
状态码定义为500(HttpStatus.INTERNAL_SERVER_ERROR.value()),即系统内部异常。
一样的,咱们须要在BrowserSecurityConfig的configure中配置它:
@Configuration public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyAuthenticationSucessHandler authenticationSucessHandler; @Autowired private MyAuthenticationFailureHandler authenticationFailureHandler; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 表单登陆 // http.httpBasic() // HTTP Basic .loginPage("/authentication/require") // 登陆跳转 URL .loginProcessingUrl("/login") // 处理表单登陆 URL .successHandler(authenticationSucessHandler) // 处理登陆成功 .failureHandler(authenticationFailureHandler) // 处理登陆失败 .and() .authorizeRequests() // 受权配置 .antMatchers("/authentication/require", "/login.html").permitAll() // 登陆跳转 URL 无需认证 .anyRequest() // 全部请求 .authenticated() // 都须要认证 .and().csrf().disable(); } }
重启项目以后,使用错误的密码登陆 图示以下:
本博文代码均通过测试,能够正常运行!
源码地址: https://github.com/ttdys/springboot/tree/master/springboot_security/02_custom_authentication