SpringSecuriyt

SpringSecurity默认的登录逻辑如下:

1、认证 -- 也就是登录的大致流程:

传统的登录,也就是通过form表单登录,使用sequrity做安全框架,当一个用户点击登录,首先security会去找 userDetailsServese 类里边的 loadUserByUsername 方法 -> 参数传递用户名 -> 拿着用户名,去数据库中查找,如果用户名存在 -> 会返回 UserDetails,而UserDetails 是一个接口,对应实现类是User,User类中的参数有用户名、密码、权限,也就是说返回的有用户名、密码和权限,接着会对前端传过来的密码和数据库返回的密码进行比对。

登录逻辑

内部通过  loadUserByUsername 接口 进行登录逻辑,

参数:就是登陆时传递的username账号

异常:用户名未找到

 返回类型

返回的类型为  UserDetails 接口

  UserDetails 接口的三个实现类

默认有三个实现了UserDetails 接口

User类

 平时开发主要用到: User类,注意与自定义的User不能混淆。

密码处理 -  PasswordEncoder接口

encode 方法,对前端传过来的密码进行加密;参数为前端传过来的密码。

matches 数据库的密码和前端传过来的密码进行匹配,参数一:前端传过来的密码,参数二:查询出来的密码;

PasswordEncoder接口的实现类

默认有非常多的实现类。

常用的  BCryptPasswordEncoder 实现类,也是官方推荐的一个。

 有三个常量

private final int strength; //密码的加密强度 默认为10

private final BCryptVersion version;

private final SecureRandom random;

 对应encode方法

大致流程

首先判断前端传过来的密码是否为空,是的话直接抛异常。有密码,接着生成盐,判断是否有随机数,没有的话使用版本号、加密强度生成盐;有的话,使用 版本号、加密强度、随机数生成盐,最后,根据哈希算法,对密码+盐进行加密,通过 strength 加密强度进行加密,加密强度默认为10。

 

测试

搞懂上边的大致流程,我们来用一个密码简单测试下,看下生成的密码和密码比对

@Test
void contextLoads() {

    BCryptPasswordEncoder bp = new BCryptPasswordEncoder();
    String psw = bp.encode("123");
    System.out.println("加密后的密码:"+ psw);

}

 下边就是对前端过传来的密码进行加密之后的样子:

对密码"123"多次执行,生成出来的加密密码也是不一样的,因为内部生成的盐每一次都是随机生成!

  查询出来的密码如何与上边加密后的密码进行比对呢, 可以使用 matches() 方法:

=======================================================

言归正传,记录下在springboot中的使用。

1、简单实现自己的登陆逻辑

如果不自己实现,默认访问应用,会弹出一个security默认的一个登录页面,默认的账号和密码是security提供的,账号admin,密码是每次启动应用随机生成的,下边写一个自己的登录逻辑:

因为要模拟密码加密,所以加一个PasswordEncoder的 Bean,下边创建了一个Security的配置类:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

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

 实现自己的登陆逻辑主要是要实现UserDetailsService接口:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println(username);
        // 账号admin,密码:123
        // 仿数据库
        if(!username.equals("admin")) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        // 比较密码,这里仿一个密码加密
        String password = passwordEncoder.encode("123");
        // 账号、密码、权限
        return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
    }
}

2、实现自己的登录页面,默认security自带的登录页,实现自己的如下:

继承

WebSecurityConfigurerAdapter ,重写 configure方法

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // ··· ···
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 表单的登录
        http.formLogin()
                .loginPage("/login.html"); // 自己的登录页面(在static中的html),注意前边加/
    }
}

3、上边我们自定义了:登录页面和登陆逻辑,此时,在浏览器访问任何api接口或应用都能访问,显然不是我们想要的,因为没有相应的权限了,没有登录,直接就能访问任何页面???

解决:

请求授权,任何请求都需要授权,接着重启服务,访问任何页面,此时浏览器崩了:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    // ··· ···
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 表单的登录
        http.formLogin()
                .loginPage("/login.html"); // 登录页面

        // 请求授权
        http.authorizeRequests()
                .anyRequest().authenticated(); // 任何请求都需要授权
    }
}

 原因:任何请求都需要授权,它会重定向到登录页面,但是登录页面也需要授权,所以死循环。

 解决:添加一组放行的路径,规定登录页面不需要授权。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 表单的登录
        http.formLogin()
                .loginPage("/login.html"); // 登录页面

        // 请求授权
        http.authorizeRequests()
                // 匹配一个可以放行的路径
                .mvcMatchers("/login.html").permitAll()
                .anyRequest().authenticated(); // 任何请求都需要授权
    }
}

4、到了我们自定义的登录页面之后,我们想要登录后直接访问之前写好的登陆成功页面:

下边是我们写好的自定义登录页面:,点击登录会请求/loigin接口,

 login接口会被下边loginProcessingUrl() 方法匹配到,然后会跳转至 /toSuccess api,这个api必须是post请求的,因为successForwardUlr() 方法只接收post请求,而且该方法的参数必须是一个控制器中定义的api接口,且控制器中返回的是一个视图名,原因在最后源码中有说明,内部只是一个简单个跳转!!!

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 表单的登录
        http.formLogin()
                .loginPage("/login.html") // 登录页面
                // 登录要处理的url
                .loginProcessingUrl("/login") // 这个路径需要跟表单提交的登录路径相同,才能走我们自定义的登陆逻辑
                // 登陆成功后跳转的页面(只接受post请求)
                .successForwardUrl("/toSuccess");

        // 关闭防火墙(关闭csrf 跨站请求伪造)
        http.csrf().disable();
        
        // 请求授权
        http.authorizeRequests()
                // 匹配一个可以放行的路径
                .mvcMatchers("/login.html").permitAll()
                .anyRequest().authenticated(); // 任何请求都需要授权
    }
}

 到控制器下的toSuccess接口,里边会重定向到success.html页面,完成一次从   登录自定义逻辑 -> 到我们自定义登录页面 -> 授权 -> 登陆成功 的请求。

 

5、登录失败失败的处理。

上边登录成功大致流程跑通了,但是如果登录失败呢,需要添加一个失败的页面:

配置ssecurity:

(1)failureForwardUrl() 失败的跳转的api。
(2)mvcMatchers("error.html") 需要添加要放行的页面,否则到不了失败页面!

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 表单的登录
        http.formLogin()
                .loginPage("/login.html") // 登录页面
                // 登录要处理的url
                .loginProcessingUrl("/login") // 这个路径需要跟表单提交的登录路径相同,才能走我们自定义的登陆逻辑
                // 登陆成功后跳转的页面(只接受post请求)
                .successForwardUrl("/toSuccess")
                // 登陆失败后跳转的页面(只接受post请求)
                .failureForwardUrl("/toError");

        // 关闭防火墙(关闭csrf 跨站请求伪造)
        http.csrf().disable();

        // 请求授权
        http.authorizeRequests()
                // 匹配一个可以放行的路径
                .mvcMatchers("/login.html", "/error.html").permitAll()
                .anyRequest().authenticated(); // 任何请求都需要授权
    }
}

 账号密码输错了:

 会转到失败页面:

 6、自定义的请求参数名

上边我们自定义的登录页面,请求参数名是:username和password,请求方法是:post,这是security默认的,必须这么写,。 

自定义参数名:

  配置:

 7、自定义页面跳转

自定义成功处理器:
successHandler(new PageSuccessHandler("http://www.baidu.com"))

 登录成功之后的页面跳转,上边是通过一个api接口,转到控制层,在由控制器里边重定向到一个html页面,但是,现在前后端分离项目,如果想直接跳转到百度页面,该如何做?

 successForwardUrl("http://www.baidu.com"),此时单纯这样写无法完成此需求!

 看下successForwardUrl源码:

 解决:

实现我们自定义的跳转,其实也很简单,实现 AuthenticationSuccessHandler 接口,重写:onAuthenticationSuccess方法即可。

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class PageSuccessHandler implements AuthenticationSuccessHandler {
    private final String url;
    public PageSuccessHandler(String url) {
        this.url = url;
    }
    @Override
    public void onAuthenticationSuccess(
            HttpServletRequest httpServletRequest, 
            HttpServletResponse httpServletResponse, 
            Authentication authentication) throws IOException, ServletException {
        // 重定向到url地址
        httpServletResponse.sendRedirect(this.url);
    }
}

修改security配置:

 

 8、来看下Authentication

下边接口在实际开发中用的很多。

onAuthenticationSuccess 方法的第三个参数 :Authentication

public class PageSuccessHandler implements AuthenticationSuccessHandler {
    private final String url;
    public PageSuccessHandler(String url) {
        this.url = url;
    }
    @Override
    public void onAuthenticationSuccess(
            HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse,
            Authentication authentication) throws IOException, ServletException {
        // 获取当前登录的这个用户
        User user = (User) authentication.getPrincipal();
        System.out.println(user.getUsername()); // 获取登录之后的用户账号
        System.out.println(user.getPassword()); // 获取登陆之后的密码,为了安全security默认为null
        System.out.println(user.getAuthorities()); // 获取当前用户的权限
        httpServletResponse.sendRedirect(this.url);
    }
}

 最后的权限是之前我们设置的:

9、定义登录失败的处理器

先来看下之前登录失败的处理方式,通过 failureForwardUrl(url)这个url必须对应的是控制器中的返回的视图,如果直接写一个xxx.html是不可行的!!!源码跟登陆成功的处理方式一样,只不过最后 onAuthenticationFailure() 授权失败的处理方法参数不一样:

 实现自定义登录失败的处理(跟成功的类似):
实现 AuthenticationFailureHandler 接口,重写 onAuthenticationFailure() 方法

package com.lxc.sequrity.config;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class PageFailHandler implements AuthenticationFailureHandler {
    private final String url;

    public PageFailHandler(String url) {
        this.url = url;
    }

    @Override
    public void onAuthenticationFailure(
            HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse,
            AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.sendRedirect(this.url);
    }
}

最后修改配置:

 登陆失败,跳转error.html成功

 

 总结:

不管登录成功的处理逻辑或登录失败的处理逻辑,其实都是需要实现成功认证的处理接口和失败的认证处理接口,重写里边的方法,在方法中定义我们的登录逻辑。其次,在配置类中 调用 :

successHandler(new 自定义的处理类) 或 failureHandler(new 自定义的处理类) 处理方法 

补充:

BCryptPasswordEncoder 和、passwordEncoder两个密码类,security内部使用的,对密码进行加密,对比等。

@Configuration
public class SecurityConfig {
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder () {
        return new BCryptPasswordEncoder();
    }
}

(2)我们自己实现登录逻辑,所以,必须要实现 userDetailsServese () 接口

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Resource
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("username:"+username);
        // 【第一步】
        // 参数:username:前端传过来的账号,拿username直接去数据库查询,如果username为null 直接抛出异常 :UsernameNotFoundException
        // 为了方便这里直接,写死
        String databaseUsername = "admin";
        if(databaseUsername.equals(username)) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        // 【第二步】
        // 密码比对
        // 我们在这拿到前端传过来的密码,springSequrity 已经帮我们加密了,此时,在这里我们只是把加密后的
        // 密码和数据库查询到的密码做比对即可。
        // 这里模拟加密,实际开发中,注册时就已经加密了,应该使用matchs()进行比对,成功返回 UserDetails
        // 下边应该写 matchers() 方法,进行密码比对,为了方便这里跳过密码比对,直接模
        // 拟一个加密后的密码
        String password = passwordEncoder.encode("123");
        return new User(username,
                password,
                // AuthorityUtils 权限的工具类
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值