SpringSecurity入门使用——登录认证

1.介绍

Spring Security,是一种基于 Spring AOP 和 Servlet 过滤器的安全框架,它提供全面的安全性解决方案。Spring Security的核心是一系列的过滤器链,通过各种过滤器在 Web 请求级和方法调用级处理身份确认和授权。

Spring Security主要有两个核心功能:

  • 登录认证
  • 资源授权

登录认证主要是指判断当前登录用户在系统中是否合法,也就是对当前用户的登录名和密码进行系统级的校验。

资源授权主要是指用户是否有权限去做某一件事,在程序面上就是指方法的访问,以及资源、数据的获取。

2.常见过滤器介绍

上面说过了,Spring Security的核心原理其实就是一系列的过滤器,当请求发送到后台时,会通过过滤器对当前请求进行判断和处理,然后根据处理的结果进行不同页面或者方法的进入。下面对Spring Security常用的几个过滤器做一个简单的介绍。

2.1FilterSecurityInterceptor

FilterSecurityInterceptor位于过滤器链的最底部,主要功能是作为一个方法级的权限过滤器,对客户请求进行过滤和处理。

下面是FilterSecurityInterceptor的源码,当前Security版本为5.3.5.

过滤器的核心都是doFilter()方法,可以看到当前过滤器对传入的参数进行了接收,并调用了invoke()方法。

查看invoke()方法,可以当前前面是对一些数据进行了判断。

如果都不为空,首先调用了beforeInvocation()方法,这个方法主要的作用是去判断再调用这个过滤器之前的其它filter是否都正常通过,因为过滤器是链式的,若其中一个发生异常,则不会再去执行之后的过滤方法。

若前面都无问题,会调用fi.getChain().doFilter(fi.getRequest(), fi.getResponse())方法,这个方法才表示真正调用后台服务去处理请求。

2.2UsernamePasswordAuthenticationFilter


这个类主要是用来处理请求登录的。通过源码可以看到,若要使Security对登录进行拦截并处理,在默认情况下必须要发送请求路径名为"login"的"POST"请求,且传入的用户名和密码的key为"username"和"possword"。

org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFil
ter
org.springframework.security.web.context.SecurityContextPersistenceFilter
org.springframework.security.web.header.HeaderWriterFilter
org.springframework.security.web.csrf.CsrfFilter
org.springframework.security.web.authentication.logout.LogoutFilter
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter
org.springframework.security.web.session.SessionManagementFilter
org.springframework.security.web.access.ExceptionTranslationFilter
org.springframework.security.web.access.intercept.FilterSecurityInterceptor

以上是SpringSecurity的主要过滤器,这里就不一一做介绍了,只调了两个比较简单的对比源码作一介绍,如果感兴趣的可以自己去查看。

3.实际使用

3.1添加依赖

加入Security的jar包,这里因为后面要使用到数据库,所以我加了msql连接和jpa的包。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

配上数据库的地址。 

spring.datasource.url=jdbc:mysql://localhost:3306/spring_security?characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai&verifyServerCertificate=false
spring.datasource.username=root
spring.datasource.password=1234

因为我没有配端口,默认启动端口为8080。当前登录页面是Security自动加的。 

3.2登录实现

3.2.1简单登录

直接启动项目,会在控制台打印一段字符串。

用此字符串作为密码,用户名为user,进行登录。因为没有做其它配置,所以结果如下。

3.2.2配置文件用户名密码配置

1.在properties配置文件中,加入下面用户名和密码配置。

spring.security.user.name=root
spring.security.user.password=1234

2.新建controller,引入thymeleaf包。并在template包下新建hello.html,用于登陆成功后页面的显示。

@Controller
public class MainController {
    @GetMapping("hello")
    public String hello(){
        return "hello";
    }
}

 

页面中做一个简单的打印。

3.请求“http://localhost:8080/hello”,会先跳转到登录页面,输入配置文件中配置的用户名和密码。登录成功,如下图所示

 

总结:这种方式是一种很简单的实现登录的方法,只是把用户名和密码写入配置文件,SpringSecurity会自动验证页面输入的数据是否和配置的数据相匹配。这种只适用于当前系统只存在单一用户,且无需数据库用户表的前提下,实际开发中,基本上都是多用户多权限角色的情况,基本上用不到当前方法。

3.2.3基于配置类实现

1.新建个配置类,继承WebSecurityConfigurerAdapter接口,并实现它的configure(AuthenticationManagerBuilder auth)方法。在该方法里面自定义用户名,密码及权限。

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /**将密码加入到内存中*/
      auth.inMemoryAuthentication()
              .withUser("admin").password("123").roles("admin");
    }

}

2.将properties中配置的用户名密码注释掉。

3.运行程序报错

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

 这个错误是因为没有将密码加密引起的,因为SpringSecurity默认存储密码的格式是"{id}xxxxxxx",id指的是加密方式可以是bcrypt、sha256等,后面跟着的是加密后的密码.也就是说,程序拿到传过来的密码的时候,会首先查找被“{”和“}”包括起来的id,来确定后面的密码是被怎么样加密的,如果找不到就认为id是null.所以这里应该为密码加密。SpringSecurity框架其实内置了加密方法,叫BCryptPasswordEncoder,只需要调用其中的encode()方法对密码进行加密就行了。

修改configure中的内容,将BCryptPasswordEncode注入进来,并将密码加密。

  @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /**将密码加入到内存中*/
      auth.inMemoryAuthentication()
                .withUser("admin").password(passwordEncoder().encode("123")).roles("admin");

    }

   @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

访问“hello”接口,输入用户名和密码后,登录成功。

总结:此方法在配置用户名密码的基础上做了一定的更新,配置用户名密码仅能同时存在一对,通过配置类的configure()方法,可以将多个用户名,密码和权限加入到内存之中。但是这种方法也有一定的确定,就是当系统角色和权限复杂时,若仅将用户名和密码写入到内存中,然后进行对比。若用户修改了名字和密码,还要去该方法中修改相应的数据,这样不仅开发麻烦,维护起来成本也很高。

3.2.4UserDetailsService接口类实现

介绍:

UserDetailsService接口是SpringSecurity中的一个重要接口,该接口里面仅有一个方法。SpringSecurity用该方法来加载已设定好的用户名、密码和权限,我们可以将已存在的数据放入数据库中,通过该方法兑取到内存之中金秀对比。这样开发维护都会变得简单。

可以看出该方法返回了一个UserDetails对象,点进源码查看。

public interface UserDetails extends Serializable {
    //获取登录用户的所有权限
    Collection<? extends GrantedAuthority> getAuthorities();
   //获取登录用户的密码
    String getPassword();
   //获取登录用户的登录名
    String getUsername();
   //当前账户是否过期
    boolean isAccountNonExpired();
   //当前账户是否锁定
    boolean isAccountNonLocked();
   //当前账户密码是否过去
    boolean isCredentialsNonExpired();
   //当前账户是否可用
    boolean isEnabled();
}

UserDetails里面有几个属性,我把每个属性代表的意思都注释到了上面,方便理解。可能有朋友会问,这些属性又是哪里来的?可以在这个类按Ctrl+H,可以看到右边实现类里面有个User对象。

打开User对象,可以看到里面有这几个属性的定义,我就不一一描述了。

使用:

1.新建两个表,用户表和角色表。

用户表:新建SysUser对象并实现UserDetails接口中的几个方法,注意这里实现的几个isXX方法,默认为false,把它们改为true。然后重写getAuthorities()方法,将用户所属权限构造GrantedAuthority对象插入到集合中。

@Entity
@Table(name = "sys_user")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysUser implements UserDetails {
    @Id
    @GeneratedValue
    private Long id;
    private String username;
    private String password;

    @ManyToMany(cascade = {CascadeType.REFRESH},fetch = FetchType.EAGER)
    private List<SysRole> roles;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> auths = new ArrayList<>();
        List<SysRole> roles = this.getRoles();
        for (SysRole role : roles) {
            auths.add(new SimpleGrantedAuthority(role.getName()));
        }
        return auths;
    }
    // 帐户是否过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    // 帐户是否被冻结
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    // 帐户密码是否过期,一般有的密码要求性高的系统会使用到,比较每隔一段时间就要求用户重置密码
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    // 帐号是否可用
    @Override
    public boolean isEnabled() {
        return true;
    }
}

角色表:

@Entity
@Table(name = "sys_role")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysRole {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
}

修改配置文件,设置为自动生成表结构。

#配置自动生成表结构
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false

查看数据库,表结构已生成。生成了三张表,分别是用户表,角色表及它们的关系表。 添加一些测试数据。

2.定义用户对象相关的Repository接口


@Repository
public interface SysUserRepository extends JpaRepository<SysUser,Long> {
}

3.定义一个类实现UserDetailsService接口,重写loadUserByUsername()方法。这里注意我数据库里的密码是直接插入进去,而不是使用加密过的密码。所以这里对于返回的SysUser对象,需要先将密码进行加密,然后在保存进对象返回。

@Service
public class MyUserDetailsService implements UserDetailsService {
    private final SysUserRepository sysUserRepository;

    public MyUserDetailsService(SysUserRepository sysUserRepository) {
        this.sysUserRepository = sysUserRepository;
    }
    @Override
    public UserDetails loadUserByUsername(String userName) {
        SysUser sysUser = sysUserRepository.getByUserName(userName);
        if (ObjectUtils.isEmpty(sysUser)) {
            throw new UsernameNotFoundException("当前用户名不存在");
        } else {
            String encode = new BCryptPasswordEncoder().encode(sysUser.getPassword());
            sysUser.setPassword(encode);
            return sysUser;
        }
    }
}

4.修改配置类的configure方法,这里直接将我们自定义的配置类注入进来。

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

5.启动项目,访问“http://localhost:8080/hello”,输入用户名和密码,登录成功。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值