前言:
在上一篇文章中,介绍了springSecurity框架搭建后,这个项目启动后,默认是必须登录后,才能进入其他页面的。而这个登录页面是springSecurity自带的登录界面。在实际的开发过程中,我们要写一个漂亮的登录界面,这就需要自定义登录页面了,那么接下来,就介绍自定义登录页面如何实现。
本篇内容包括:自定义登录页面,自定义成功拦截器,自定义失败拦截器,自定义登录表单名称,action。
一、步骤
- 创建登录界面
login.html
。 - 创建路径映射对应的
Controller
。 - 创建
WebSecurityConfigurerAdapter
的子类WebSecurityConfigurer
,用@Configuration
注解该子类。-
@Configuration
中所有使用@Bean
注解的方法都会被动态代理,一次调用该方法返回的都是同一个实例。 - 并重写里面的
configure()
方法。 - 并创建
passwordEncoder()
方法。在方法上使用@Bean
注解。
-
- 创建
UserDetailsService
的实现类MyUserDetails
类中,用@Component
注解。- 使用
@Autowired
,注入PasswordEncoder对象 。 - 重写认证根据用户名认证用户的方法
loadUserByUsername()
。 - 设置用户的密码,角色。
- 使用
- 在
WebSecurityConfigurerAdapter
的子类中WebSecurityConfigurer
,注入UserDetailsService
的实现类,并在config()
方法中配置。 - 分别创建自定义登录成功,失败处理器,对应实现
AuthenticationSuccessHandler
,AuthenticationFailureHandler
接口。使用@Component
将它们放入容器中。以备WebSecurityConfigurerAdapter
的子类使用。 - 在
WebSecurityConfigurerAdapter
的子类中WebSecurityConfigurer
,注入AuthenticationSuccessHandler
,AuthenticationFailureHandler
接口的处理器实现类,并在config
进行配置。 -
application.properties
中配置视图映射 。
注意:springboot使用的是使用
thymeleaf
的html5模板。
表结构.png查看项目结构示意图。
二、清单说明
清单一、springboot是使用thymeleaf
的html5模板。
步骤1.1中创建
login.html
页面,头部<html>
中,标签中引入<html lang="en" xmlns:th="http://www.thymeleaf.org">
清单二、springboot约定的资源文件映射路径
springboot
约定的资源文件映射路径:
gradle
项目资源目录:src/main/java/resources
spring-boot
项目静态文件目录:/src/main/java/resources/static
spring-boot
项目模板文件目录:/src/main/java/resources/templates
步骤1.8中配置
application.properties
中配置模板视图映射。springboot因为已经默认约定好了资源文件映射路径。其实在这里可以不用配置。我还是要把它,贴在这里,一边更好的来理解。
#视图 这是默认的配置
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
#检查模板是否存在,然后再呈现
spring.thymeleaf.check-template-location=true
清单三 、spring-security登录界面的name。
在spring-security
中默认设置的用户登录表单中的name
名称为username
和password
。
默认设置的登录表单中name名称,用户名密码。同时还默认设置了登录页面表单请求的
action=/login
,以及必须为post
方式请求。可以在spring-security源码中查看:UsernamePasswordAuthenticationFilter
。
【扩展阅读】节选源码片段一:UsernamePasswordAuthenticationFilter 定义的登录名单中name名称
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = "username";
private String passwordParameter = "password";
private boolean postOnly = true;
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
// ... ...
}
清单四 、login.html。
<div align="center">
<h2>自定义登录页面</h2>
<form th:action="@{/login}" method="post">
<label>
<span>用户名:</span>
<input type="text" name="username">
<!--<input type="text" name="uname">-->
</label>
<br/>
<label>
<span>密码:</span>
<input type="password" name="password">
<!--<input type="password" name="psd">-->
</label>
<input type="submit" class="submit input_button" value="登录">
</form>
</div>
注意:
【要点1】form 表单提交的action 是login
,及和登录页面同名(和路径映射的controller同名)。
- 如果你改成其他的action,比如说为:
/login/form
,则需要在WebSecurityConfigurerAdapter
的子类
的 config()方法中添加.loginProcessingUrl("/login/form") //form 表单action
2.如果form表单的action如果和登录页面同名(和路径映射的controller同名)【不一定非得是login】,那么无需添加.loginProcessingUrl("")
这一句,它会默认请求。
3.以上1,2两种方式,action的值是否和请求页面路径的controller映射一致,都不需要去写action对应路径的controller路径。
【要点2】form表单中用户名密码,自定义
1.需要在WebSecurityConfigurerAdapter
的子类
的 config()方法中添加.usernameParameter("uname")
和.passwordParameter("psd")
。
清单五、路径映射的controller
步骤1.2 创建路径映射对应的Controller 。
@Controller
public class LoginController {
@GetMapping("/login")
public String login() {
return "login";
}
}
清单六、创建失败,成功处理器
步骤:1.6 分别创建自定义登录成功,失败处理器,对应实现AuthenticationSuccessHandler,AuthenticationFailureHandler接口。使用@Component将它们放入容器中。以备WebSecurityConfigurerAdapter 的子类使用。
代码1:失败处理器
@Component
public class FailureAuthenticationHandler implements AuthenticationFailureHandler {
protected Logger logger=LoggerFactory.getLogger(getClass());
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
logger.info("登录失败");
httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(e));
// httpServletResponse.getWriter().write(objectMapper.writeValueAsString(new SimpleResonse(e.getMessage())));
}
}
代码2:成功处理器
@Component
public class SuccessAuthenticationHandler implements AuthenticationSuccessHandler {
protected Logger logger=LoggerFactory.getLogger(getClass());
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
logger.info("登录成功");
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authentication)+"/n"+"登录成功了");
}
}
清单七、创建用户信息:定义登录密码
步骤:1.4 创建
UserDetailsService
的实现类MyUserDetails
。
@Component
public class MyUserDetails implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String password=passwordEncoder.encode("123456");
return new User(username,password,AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
清单八、创建WebSecurityConfigurerAdapter的子类 WebSecurityConfigurer
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
private FailureAuthenticationHandler failureHandler;
@Autowired
private SuccessAuthenticationHandler successHandler;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义login.html登录界面
http.formLogin().loginPage("/login")
// .loginProcessingUrl("/login) //form 表单action
.failureHandler(failureHandler)
.successHandler(successHandler)
//.usernameParameter("uname")
//.passwordParameter("psd")
.and()
.authorizeRequests()
.antMatchers("/login").permitAll()
// 除了login.html页面以外都需要身份认证 (一定要记得添加这一句,否则就是死循环)
.anyRequest()
.authenticated()
;
}
}
注意:
【要点1】:一定要把自定义登录界面给放开权限。
即.antMatchers("/login").permitAll()
,否则就是死循环。
最后运行项目 ,
登录:用户名,随便输入,密码为:123456
登录成功后,会显示
{"authorities":[{"authority":"admin"}],"details":{"remoteAddress":"127.0.0.1","sessionId":"7BD95EAEB7B20BDAA5CCB18C0C601684"},"authenticated":true,"principal":{"password":null,"username":"2222","authorities":[{"authority":"admin"}],"accountNonExpired":true,"accountNonLocked":true,"credentialsNonExpired":true,"enabled":true},"credentials":null,"name":"2222"}/n登录成功了
登录失败了,会显示
{"cause":null,"stackTrace":[{"methodName":"additionalAuthenticationChecks","fileName":"DaoAuthenticationProvider.java","lineNumber":89,"className":"org.springframework.security.authentication.dao.DaoAuthenticationProvider","nativeMethod":false},{"methodName":"authenticate","fileName":"AbstractUserDetailsAuthenticationProvider.java","lineNumber":166,"className":"org.springframework.security.authentication.dao.AbstractUserDetailsAuthentication
Provider","nativeMethod":false},
// ... ... 省略好多内容
{"methodName":"run","fileName":"Thread.java","lineNumber":748,"className":"java.lang.Thread","nativeMethod":false}],"localizedMessage":"坏的凭证","message":"坏的凭证","suppressed":[]}
网友评论