登录成功后的处理AuthenticationSuccessHandler:
认证成功后,默认情况下spring security会继续访问之前访问的url,如果想自定义处理逻辑,用默认的就不行了。此时需要自定义登录成功后的处理,springsecurity提供了一个接口,AuthenticationSuccessHandler:
有三个参数,request,response,authentication(封装认证的信息,认证请求里的信息ip、session等)
public interface AuthenticationSuccessHandler { /** * Called when a user has been successfully authenticated. * * @param request the request which caused the successful authentication * @param response the response * @param authentication the <tt>Authentication</tt> object which was created during * the authentication process. */ void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException; }
这个接口只有一个方法,登录成功后就被调用。
1、自定义一个handler,实现这个接口:
package com.imooc.security.browser.authentication; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.ObjectMapper; /** * 认证成功后做的处理 * ClassName: ImoocAuthenticationSuccessHandler * @Description: 认证成功后做的处理 * @author lihaoyang * @date 2018年3月1日 */ @Component("imoocAuthenticationSuccessHandler") public class ImoocAuthenticationSuccessHandler implements AuthenticationSuccessHandler{ private Logger logger = LoggerFactory.getLogger(getClass()); //springmvc启动会自动注册一个ObjectMapper @Autowired private ObjectMapper objectMapper; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { logger.info("登录成功"); //把authentication返回给响应 response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(authentication)); } }
2、添加到配置类,让spring security执行自定义的处理器
package com.imooc.security.browser; 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; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import com.imooc.security.core.properties.SecurityProperties; @Configuration //这是一个配置 public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter{ //读取用户配置的登录页配置 @Autowired private SecurityProperties securityProperties; //自定义的认证后的处理器 @Autowired private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler; //注意是org.springframework.security.crypto.password.PasswordEncoder @Bean public PasswordEncoder passwordencoder(){ //BCryptPasswordEncoder implements PasswordEncoder return new BCryptPasswordEncoder(); } //版本一:配置死的登录页 // @Override // protected void configure(HttpSecurity http) throws Exception { // //实现需要认证的接口跳转表单登录,安全=认证+授权 // //http.httpBasic() //这个就是默认的弹框认证 // http.formLogin() //表单认证 // .loginPage("/login.html") //登录页面 // //登录过滤器UsernamePasswordAuthenticationFilter默认登录的url是"/login",在这能改 // .loginProcessingUrl("/authentication/form") // .and() // .authorizeRequests() //下边的都是授权的配置 // .antMatchers("/login.html").permitAll() //放过登录页不过滤,否则报错 // .anyRequest() //任何请求 // .authenticated() //都需要身份认证 // .and() // .csrf().disable() //关闭csrf防护 // ; // } //版本二:可配置的登录页 @Override protected void configure(HttpSecurity http) throws Exception { //实现需要认证的接口跳转表单登录,安全=认证+授权 //http.httpBasic() //这个就是默认的弹框认证 http.formLogin() //表单认证 .loginPage("/authentication/require") //处理用户认证BrowserSecurityController //登录过滤器UsernamePasswordAuthenticationFilter默认登录的url是"/login",在这能改 .loginProcessingUrl("/authentication/form") .successHandler(imoocAuthenticationSuccessHandler)//自定义的认证后处理器 .and() .authorizeRequests() //下边的都是授权的配置 // /authentication/require:处理登录,securityProperties.getBrowser().getLoginPage():用户配置的登录页 .antMatchers("/authentication/require",securityProperties.getBrowser().getLoginPage()).permitAll() //放过登录页不过滤,否则报错 .anyRequest() //任何请求 .authenticated() //都需要身份认证 .and() .csrf().disable() //关闭csrf防护 ; } }
启动springboot,访问localhost:8080/index.html ,跳转到登录页:
登录,响应了自定义的处理器,返回了Authentication的json:其中粉色部分是自定义的登录处理MyUserDetailService类的loadUserByUsername方法返回值User类对应的json ,而且spring security还会自动把密码置为null
{ "authorities": [{ "authority": "admin" }], "details": { "remoteAddress": "0:0:0:0:0:0:0:1", "sessionId": "DBDC15FA0FFEFD35B011431454B4F466" }, "authenticated": true, "principal": { "password": null, "username": "user", "authorities": [{ "authority": "admin" }], "accountNonExpired": true, "accountNonLocked": true, "credentialsNonExpired": true, "enabled": true }, "credentials": null, "name": "user" }
MyUserDetailService:
package com.imooc.security.browser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; /** * UserDetailsService是SpringSecurity的一个接口, * 只有一个方法:根据用户名获取用户详情 * ClassName: MyUserDetailService * @Description: TODO * @author lihaoyang * @date 2018年2月28日 */ @Component public class MyUserDetailService implements UserDetailsService{ private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private PasswordEncoder passwordEncoder; /** * UserDetails接口,实际可以自己实现这个接口,返回自己的实现类 */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { logger.info("登录用户名:"+username); //根据用户名查询用户信息 //User:springsecurity 对 UserDetails的一个实现 //为了演示在这里用passwordEncoder加密一下密码,实际中在注册时就加密,此处直接拿出密码 String password = passwordEncoder.encode("123456"); System.err.println("加密后密码: "+password); //参数:用户名|密码|是否启用|账户是否过期|密码是否过期|账户是否锁定|权限集合 return new User(username,password,true,true,true,true,AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); } }
登录失败的处理AuthenticationFailureHandler:
和登录成功的处理一样
package com.imooc.security.browser.authentication; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.ObjectMapper; /** * 登录失败后的处理 * ClassName: ImoocAuthenticationFailureHandler * @Description: 登录失败后的处理 * @author lihaoyang * @date 2018年3月1日 */ @Component("imoocAuthenticationFailureHandler") public class ImoocAuthenticationFailureHandler implements AuthenticationFailureHandler { private Logger logger = LoggerFactory.getLogger(getClass()); //springmvc启动会自动注册一个ObjectMapper @Autowired private ObjectMapper objectMapper; @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { logger.info("登录失败"); //把authentication返回给响应 //状态码500,服务器内部错误 response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(exception)); } }
在java配置类里注入,然后配置一下就行了
启动springboot,访问登录页http://localhost:8080/demo-login.html,填写错误的密码:
响应500,坏的凭证。和一些堆栈信息
到目前为止,自定义登录处理逻辑已经做完了,此时做的效果都是返回的json,如果系统里是登录成功后跳转页面的,这样还不够通用,需要做成返回json和重定向两种都适应的。
要的效果是,能够在application.properties里做登录类型的配置。如:
需要在配置类 SecurityProperties 里加上这个loginType,新建枚举类:
package com.imooc.security.core.properties; /** * 登录类型枚举 * ClassName: LoginType * @Description: 登录类型 * @author lihaoyang * @date 2018年3月1日 */ public enum LoginType { /** * 返回json */ JSON, /** * 重定向 */ REDIRECT }
BrowserProperties :
package com.imooc.security.core.properties; /** * 浏览器配置项 * ClassName: BrowserProperties * @Description: 浏览器配置项 * @author lihaoyang * @date 2018年2月28日 */ public class BrowserProperties { //用户未配置默认登录页 private String loginPage = "/login.html"; 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; } }
动态配置登录类型
改造登录处理器:
登录成功处理器改造为:
继承一个spring默认的实现类 extends SavedRequestAwareAuthenticationSuccessHandler ,引入配置类,就能读取properties配置文件,进行判断了:
package com.imooc.security.browser.authentication; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.ObjectMapper; import com.imooc.security.core.properties.BrowserProperties; import com.imooc.security.core.properties.LoginType; import com.imooc.security.core.properties.SecurityProperties; /** * 认证成功后做的处理 * ClassName: ImoocAuthenticationSuccessHandler * @Description: 认证成功后做的处理 * @author lihaoyang * @date 2018年3月1日 */ @Component("imoocAuthenticationSuccessHandler") public class ImoocAuthenticationSuccessHandler //spring默认的登录成功处理器,实现了AuthenticationSuccessHandler接口,适配登录后 重定向和返回json两种用这个实现 extends SavedRequestAwareAuthenticationSuccessHandler //返回json用这个实现 /*implements AuthenticationSuccessHandler*/{ private Logger logger = LoggerFactory.getLogger(getClass()); //springmvc启动会自动注册一个ObjectMapper @Autowired private ObjectMapper objectMapper; @Autowired private SecurityProperties securityProperties; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { logger.info("登录成功"); /** * 判断配置的登录类型,做不同处理 */ if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){ //响应json //把authentication返回给响应 response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(authentication)); }else{ //调用父类的方法,默认就是重定向 super.onAuthenticationSuccess(request, response, authentication); } } }
登录失败的处理改造为:
package com.imooc.security.browser.authentication; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.ObjectMapper; import com.imooc.security.core.properties.BrowserProperties; import com.imooc.security.core.properties.LoginType; import com.imooc.security.core.properties.SecurityProperties; /** * 登录失败后的处理 * ClassName: ImoocAuthenticationFailureHandler * @Description: 登录失败后的处理 * @author lihaoyang * @date 2018年3月1日 */ @Component("imoocAuthenticationFailureHandler") public class ImoocAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler /*implements AuthenticationFailureHandler*/ { private Logger logger = LoggerFactory.getLogger(getClass()); //springmvc启动会自动注册一个ObjectMapper @Autowired private ObjectMapper objectMapper; @Autowired private SecurityProperties securityProperties; @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { logger.info("登录失败"); if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){ //把authentication返回给响应 //状态码500,服务器内部错误 response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(exception)); }else{ super.onAuthenticationFailure(request, response, exception); } } }
这样,在application.properties 里配置 imooc.security.browser.loginType = REDIRECT 或 imooc.security.browser.loginType = JSON 就可以动态控制登录成功后改返回json还是重定向了。
在demo项目新建一个index.html:
<body>
<h2>Index Page</h2> <br>
</body>
配置登录类型为REDIRECT,访问 localhost:8080/index.html 让登录
登录成功后,重定向到了index.html
设置为JSON登录:
#登录类型
imooc.security.browser.loginType = JSON
再访问localhost:8080/index.html :
响应我们自己处理的登录逻辑。
完整代码放在了github :https://github.com/lhy1234/spring-security