[SpringSecurity]之二:自定义登录

版权声明:无需授权即可转载,甚至无需保留以上版权声明... ... https://blog.csdn.net/qq_28296925/article/details/82048588

前言:
在上一篇文章中,介绍了springSecurity框架搭建后,这个项目启动后,默认是必须登录后,才能进入其他页面的。而这个登录页面是springSecurity自带的登录界面。在实际的开发过程中,我们要写一个漂亮的登录界面,这就需要自定义登录页面了,那么接下来,就介绍自定义登录页面如何实现。
本篇内容包括:自定义登录页面,自定义成功拦截器,自定义失败拦截器,自定义登录表单名称,action。

一、步骤

  1. 创建登录界面login.html
  2. 创建路径映射对应的Controller
  3. 创建WebSecurityConfigurerAdapter 的子类WebSecurityConfigurer,用@Configuration注解该子类。
    • @Configuration中所有使用@Bean注解的方法都会被动态代理,一次调用该方法返回的都是同一个实例。
    • 并重写里面的configure()方法。
    • 并创建passwordEncoder()方法。在方法上使用@Bean注解。
  4. 创建UserDetailsService的实现类MyUserDetails类中,用@Component注解。
    • 使用@Autowired,注入PasswordEncoder对象 。
    • 重写认证根据用户名认证用户的方法loadUserByUsername()
    • 设置用户的密码,角色。
  5. WebSecurityConfigurerAdapter 的子类中WebSecurityConfigurer,注入UserDetailsService的实现类,并在config()方法中配置。
  6. 分别创建自定义登录成功,失败处理器,对应实现AuthenticationSuccessHandlerAuthenticationFailureHandler接口。使用@Component将它们放入容器中。以备WebSecurityConfigurerAdapter 的子类使用。
  7. WebSecurityConfigurerAdapter 的子类中WebSecurityConfigurer,注入 AuthenticationSuccessHandlerAuthenticationFailureHandler接口的处理器实现类,并在config进行配置。
  8. application.properties中配置视图映射 。

注意:springboot使用的是使用thymeleaf的html5模板。

查看项目结构示意图。
项目结构


二、清单说明

清单一、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名称为usernamepassword

默认设置的登录表单中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同名)。
1. 如果你改成其他的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":[]}

猜你喜欢

转载自blog.csdn.net/qq_28296925/article/details/82048588