文档章节

精通Spring Boot ——第十七篇:Spring Security自定义登录逻辑

liululee
 liululee
发布于 2018/11/25 21:11
字数 1301
阅读 429
收藏 4

本章的内容较为简单,暂时先不涉及数据库的操作,只是为了演示,在Spring Security 中,如何来处理用户登录的流程。 想要了解用户登录的逻辑,我们只需要处理三个问题:a.用户信息获取逻辑 b.如何校验用户信息 c.处理用户加密解密

用户信息获取 UserDetailsService

用户信息的获取逻辑在Spring Security 中是被封装在一个叫UserDetailsService的接口里的,在这个接口中,只有一个方法,loadUserByUsername()。接口接收一个String参数,返回一个UserDetails对象。顾名思义,就是要传递我们的username,然后根据这个username去找到这个User的用户信息。如果找不到这个用户,就会抛出一个UsernameNotFoundException,Spring Security 捕获到这个异常,就会返回相应的错误信息。

package org.springframework.security.core.userdetails;

public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

接下来,我们自己写一个类,实现这个UserDetailsService

@Component
public class MyUserDetailsService implements UserDetailsService {
    
	private static final Logger logger = LoggerFactory.getLogger(MyUserDetailsService.class);

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        logger.info("登录的用户名:" + username);
        return new User("admin","123456", AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

我们调用了这个loadUserByUsername方法,要返回的是UserDetails对象。可以看到代码中我是直接返回一个Spring Security中的User对象。这个User对象的源码我们来看看,它提供了两种构造方法

/**
 * 这个构造方法三个参数:username代表用户名,password代表密码,第三个参数 authorities表示用户权限的集合。而这个方法具体实现则是调用了另 
 * 个构造方法。并且多了四个值为true的参数。接下来看另一个构造方法。
 */
 public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        this(username, password, true, true, true, true, authorities);
    }
/**
 * enabled:用户是否启用,accountNonExpired:用户是否过期,
 * credentialsNonExpired:用户凭证是否过期,accountNonLocked:用户是否被锁定。
 */
public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
        if (username != null && !"".equals(username) && password != null) {
            this.username = username;
            this.password = password;
            this.enabled = enabled;
            this.accountNonExpired = accountNonExpired;
            this.credentialsNonExpired = credentialsNonExpired;
            this.accountNonLocked = accountNonLocked;
            this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
        } else {
            throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
        }
    }

用户信息校验 UserDetails

User对象和UserDetails又是什么关系呢? 现在一起来看看UserDetails对象的源码实现

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

UserDetails是个接口(interface),那难道说是User对象实现了UserDetails接口吗?答案是肯定的。

public class User implements UserDetails, CredentialsContainer {
    // 省略代码
}

isAccountNonExpired(标识用户是否过期了,也就是用户是否被冻结,有些应用可能需要) isAccountNonLocked(标识用户是否被锁定,一般用于用户输入密码错误次数的验证) isCredentialsNonExpired(标识用户密码是否过期,有些网站为了安全性起见,可能会对用户密码的使用时间做验证) isEnabled(用户是否启用,一般来说,对于应用,我们是不会删除用户数据的,如果用户注销的话,我们只需标识用户不可用就好了) 如果以上判断中,并不需要用到,那只要返回true就好了。

用户信息加解密 PasswordEncoder

再提一下用户对用户密码加解密的方法: PasswordEncoder。 先来写个配置类

@Configuration
@EnableWebSecurity // 启用Spring Security
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	// 注入 MyUserDetailsSerivce
    @Autowired
    private MyUserDetailsService userDetailsService;
	// 创建PasswordEncoder Bean
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
		// 设置form 登录方式,且成功跳转至hello页面
        http.formLogin().successForwardUrl("/hello")
				// 设置所有的请求都要经过验证
              .and().authorizeRequests().anyRequest().authenticated()
               // 设置自定义的UserDetailsService
			  .and().userDetailsService(userDetailsService);
			 
    }
}

OK, 接下来我们稍微对我们自定义的UserDetailsService实现做些变化,利用passwordEncoder来加密我们的密码。

@Component
public class MyUserDetailsService implements UserDetailsService {
    private static final Logger logger = LoggerFactory.getLogger(MyUserDetailsService.class);

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        logger.info("登录的用户名:" + username);
        String password = passwordEncoder.encode("123456");
        logger.info("加密后的密码:" + password);
        return new User("admin", password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

接着,写一个简单的controller

/**
 * @author developlee
 * @since 2018/11/25 20:40
 */
@RestController
public class HelloController {
    @PostMapping("/hello")
    public String hello(){
        return "Hello ! My Dear Friend !";
    }
}

做个简单的验证,让我们来访问下,http:localhost:8080/hello

跳转至Spring Security为我们提供登录页面。 输入用户名密码

可以看到, 每次加密过的密码都是不一样的。这里面的机制,其实就是密码中常用的加盐方式。

总结

这篇算是真正踏入Spring Security的入门,了解了Spring Security 中最基本也是几个类及方法,User,UserDetails,UserDetailsService,以及PasswordEncoder。 接下来的文章中,将更加深入去探讨Spring Security的登录流程,权限控制等。

以上代码均可在我的github.com中找到,感谢花时间阅读,如果文章中有错误或不足之处,烦请赐教,共同探讨。

© 著作权归作者所有

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

评论(1)

葛世杰
葛世杰
高产似内啥,,,😆 先评再看
Spring Security 从入门到进阶系列教程

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

小致Daddy
2018/08/03
19.2K
1
SpringBoot集成Spring Security(4)——自定义表单登录

通过前面三篇文章,应该大致了解了Spring Security的流程。你应该发现了,真正的登录请求是由Spring Security帮我们处理的,那么我们如何实现自定义表单登录呢,比如添加一个验证码… 源码地...

yuanlaijike
2018/05/09
0
0
精通Spring Boot—— 第二十篇:Spring Security记住我功能

引言 本章的代码实现是在上一篇教程:精通Spring Boot——第十九篇:Spring Security 整合验证码登录基础上,如果感觉本篇跳跃幅度较大,可先阅读上一篇,或访问我的github.com(文末会附上地...

liu浪诗人
02/07
96
2
精通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.7K
3
springboot微服务系列教程

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

鱼煎
01/13
311
0

没有更多内容

加载失败,请刷新页面

加载更多

CSS--列表

一、列表标识项 list-style-type none:去掉标识项 disc:默认实心圆 circle:空心圆 squire:矩形 二、列表项图片 list-style-img: 取值:url(路径) 三、列表项位置 list-style-position:...

wytao1995
今天
6
0
linux 命令-文本比较comm、diff、patch

本文原创首发于公众号:编程三分钟 今天学了三个文本比较的命令分享给大家。 comm comm 命令比较相同的文本 $ cat charabc$ cat chardiffadc 比如,我有两个文件char和chardiff如上,...

编程三分钟
今天
7
0
QML教程

https://blog.csdn.net/qq_40194498/article/category/7580030 https://blog.csdn.net/LaineGates/article/details/50887765...

shzwork
今天
5
0
HA Cluster之5

对于使用heartbeat v2版的CRM配置的集群信息都是保存在一个名为cib.xml的配置文件中,存放在/var/lib/heartbeat/crm/下。CIB:Cluster Information Base,由于xml文件配置不是那么方便,所以...

lhdzw
今天
6
0
玩转Redis-Redis基础数据结构及核心命令

  《玩转Redis》系列文章主要讲述Redis的基础及中高级应用,文章基于Redis5.0.4+。本文主要讲述Redis的数据结构String,《玩转Redis-Redis基础数据结构及核心命令》相关操作命令为方便对比...

zxiaofan666
今天
11
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部