文档章节

精通Spring Boot——第十八篇:Spring Security 自定义认证流程

liululee
 liululee
发布于 2018/12/07 21:09
字数 1279
阅读 325
收藏 8

前两篇简单介绍了一下使用Spring Security 使用Http Basic登录,以及Spring Security如何自定义登录逻辑。这篇文章主要介绍如何使用handler来定义认证相关的流程。 先做一些自定义的操作,如配置自定义登录页,配置登录请求URL等。 当我们使用Spring Security时,它会为我们提供一个默认的登录页面,这显然没法满足我们的需求,那如何来自定义页面呢?请看代码:

/**
 * @author developlee
 * @since 2018/11/27 21:58
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final MyLoginHandler myLoginHandler;

    private final MyLogoutHandler myLogoutHandler;

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Autowired
    private MyUserDetailsService myUserDetailsService;

    @Autowired
    public SecurityConfig(MyLoginHandler myLoginHandler, MyLogoutHandler myLogoutHandler) {
        this.myLoginHandler = myLoginHandler;
        this.myLogoutHandler = myLogoutHandler;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 自定义用户登录页,并允许客户端请求
        http.formLogin().loginPage("/login").permitAll()
                .loginProcessingUrl("/sign_in")
                // 配置登录成功的handler
                .successHandler(myLoginHandler)
                .and().authorizeRequests().anyRequest().authenticated();
        // 配置登出的handler
        http.logout().addLogoutHandler(myLogoutHandler)
                 // logout 成功,删除 cookies
         .deleteCookies("web-site", "custom-token").clearAuthentication(true);
                 // Spring Security 默认是开启了CSRF 保护的,所以logout操作必须是用POST方式请求,
                 // 如果非要使用GET请求来logout的话,也可以在代码中的实现
                 //.logoutRequestMatcher(new AntPathRequestMatcher("/logout","GET"))
        //session管理   session失效后跳转
        http.sessionManagement().invalidSessionUrl("/login");
        //只允许一个用户登录,如果同一个账户两次登录,那么第一个账户将被踢下线,跳转到登录页面
        http.sessionManagement().maximumSessions(1).expiredUrl("/login");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder());
        auth.eraseCredentials(false);
    }
}

接下来写个简单的登录页,这个页面我是用thymeleaf模板写的,也是第一次使用thymeleaf,还请大家多多包涵这丑陋的画风。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Thymeleaf-Login-Demo</title>
</head>
<body>
    <div id="header">
        <h2>登录示例页面-供参考</h2>
        <strong>demo login page for example</strong>
    </div>
    <div id="container">
        <form th:action="@{/sign_in}" method="post">
            <input name="username" type="text" placeholder="用户名"/>
            <br/>
            <input name="password" type="password" placeholder="密码"/>
            <br/>
            <input name="登录" type="submit" />
            <br/>
        </form>
    </div>
</body>
</html>

项目启动起来,看到的页面效果图如下:

结合数据库来实现用户登录,按照我们的思路,实现UserDetails, UserDetailsService 这两接口。首先,让我们自己的User实体类实现UserDetails接口. 自定义User实体类,这个类和我们的数据库结构是对应的。

/**
 * @author developlee
 * @since 2018/11/27 21:38
 */
@Entity
@Table(name = "tb_users")
@Data
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "username")
    private String username;

    @Column(name = "password")
    private String password;

    @Column(name = "age")
    private String age;

    @Column(name = "sex")
    private String sex;

    @Column(name = "isLock")
    private boolean isLock;

    @Column(name = "isEnabled")
    private boolean isEnabled;

}

实现UserDetailsService接口,重写loadUserByUsername方法

@Service
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 从数据库查询用户
        User user = userRepository.findByUsername(username);
        if(user == null) {
             throw new UsernameNotFoundException("用户" + username + "不存在");
        }
        return new MyUserDetails(user);
    }
}

接下来,自定义MyUserDetails实现UserDetails接口,构造方法传入我们从数据库查询出来的User对象。

/**
 * @author developlee
 * @since 2018/11/27 21:42
 */
public class MyUserDetails implements UserDetails {

    private User user;

    public MyUserDetails(User user) {
        this.user = user;
    }


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return user.isLock();
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return user.isEnabled();
    }
}

搞个登录成功的处理器MyLoginHandler,登录成功后,打印一行日志,并跳转到hello页

@Slf4j
@Component
public class MyLoginHandler implements AuthenticationSuccessHandler {

    // 登录成功处理
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        log.info("登录成功!");
        httpServletResponse.sendRedirect(httpServletRequest.getContextPath().concat("/hello"));
    }
}

登录试试看吧,见证奇迹的时刻 这是数据库中创建的用户

生成密码的代码

  @Autowired
    private PasswordEncoder passwordEncoder;

    @Test
    public void testMac() {
        String password = "123456";
        System.out.println("加密后密码:" + passwordEncoder.encode(password));
    }

密码生成插入数据库,应该在用户注册时进行操作。

然后,让我们在hello页,新增一个logout按钮,来实现登出功能。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Hello</title>
</head>
<body>
    <p>Hello, World!</p>
    <a id="logout-btn" th:href="@{/sign_out}">我要退出!</a>
</body>
</html>

点击‘我要退出!’即可跳转到登录页! 现在登录登出我们都已经准备就绪,接下来,为我们的登录加些料吧! 默认的登出链接是/logout,如果开启了CSRF验证(默认是开启的),则该登出请求,必须设置为post请求。登出后浏览器跳转路径模式/login?logout。SecurityContextLogoutHandler 默认是作为最后的logoutHandler的。在处理登出请求中,我们可以自己添加logoutHandler或者LogoutSuccessHandler的实现。接下来请看代码演示: logoutHandler处理器

@Slf4j
@Component
public class MyLogoutHandler implements LogoutHandler {
    @Override
    public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
        log.info("登出成功了!!!");
        authentication.setAuthenticated(false); // 设置为未授权
    }
}

本文的所有代码我已经放在我的github.com上,感谢您的观看,如果有什么错误的地方,还请指出,共同探讨!

© 著作权归作者所有

liululee
粉丝 127
博文 66
码字总数 90743
作品 0
杭州
程序员
私信 提问
加载中

评论(1)

葛世杰
葛世杰
来晚啦,,,先赞后看
精通Spring Boot——第十六篇:初探Spring Security,使用Http Basic认证

说明 本文以及接下来有关spring security 的文章, 基于Spring Boot 2.1.0 RELEASE , Spring Security 5.1.2RELEASE 简单介绍Spring Security Spring Security是当今非常流行的,基于Spring提...

liu浪诗人
2018/11/25
2.8K
3
Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密...

小致Daddy
2018/08/03
19.4K
1
Spring Security4实战与原理分析视频课程( 扩展+自定义)

Spring Security概述与课程概要介绍 Spring Security快速入门(基于XML) Spring Security快速入门(基于XML) URL匹配详解 自定义登陆 配置退出 Ajax登陆退出 JDBC认证 层级角色关系 认证体...

刘宗泽
2018/06/26
0
0
Spring Boot学习笔记

多模块开发 [SpringBoot学习]-IDEA创建Gradle多Module结构的SpringBoot项目 RabbitMQ RabbitMQ 安装 linux安装RabbitMQ详细教程 Ubuntu 16.04 RabbitMq 安装与运行(安装篇) ubantu安装...

OSC_fly
2018/07/26
0
0
springboot微服务系列教程

微服务系列教程 Spring Boot 2是微服务的基础,在2018年3月初,Spring Boot 正式宣布进入2.0时代。下面一起来步入Spring Boot 2时代,领略Spring Boot 2带来的开发乐趣吧! 本系统源代码地址...

鱼煎
01/13
321
0

没有更多内容

加载失败,请刷新页面

加载更多

【0918】正则介绍_grep

【0918】正则介绍_grep 9.1 正则介绍_grep上 9.2 grep中 9.3 grep下 一、正则介绍 正则是一串有规律的字符串,它使用单个字符串来描述或匹配一系列符合某个语法规则的字符串。 二、grep工具 ...

飞翔的竹蜻蜓
35分钟前
4
0
为什么要在网站中应用CDN加速?

1. 网页加载速度更快 在网站中使用CDN技术最直接的一个好处就是它可以加快网页的加载速度。首先,CDN加速的内容分发是基于服务器缓存的,由于CDN中缓存了不少数据,它能够给用户提供更快的页...

云漫网络Ruan
今天
8
0
亚玛芬体育(Amer Sports)和信必优正式启动合作开发Movesense创新

亚玛芬体育和信必优正式启动合作开发Movesense创新,作为亚玛芬体育的完美技术搭档,信必优利用Movesense传感器技术为第三方开发移动应用和服务。 Movesense基于传感器技术和开放的API,测量...

symbiochina88
今天
4
0
创龙TI AM437x ARM Cortex-A9 + Xilinx Spartan-6 FPGA核心板规格书

SOM-TL437xF是一款广州创龙基于TI AM437x ARM Cortex-A9 + Xilinx Spartan-6 FPGA芯片设计的核心板,采用沉金无铅工艺的10层板设计,适用于高速数据采集和处理系统、汽车导航、工业自动化等领...

Tronlong创龙
今天
5
0
好程序员Java学习路线分享MyBatis之线程优化

  好程序员Java学习路线分享MyBatis之线程优化,我们的项目存在大量用户同时访问的情况,那么就会出现大量线程并发访问数据库,这样会带来线程同步问题,本章我们将讨论MyBatis的线程同步问...

好程序员官方
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部