[学习笔记] SpringSecurity之SpringSecurity入门(一)

# 学习 # · 2021-10-20

SpringSecurity简介

1、安全框架:解决系统安全问题的框架。使用安全框架,我们可以通过配置的方式实现对资源的访问限制。

2、常见的安全框架:

(1)SpringSecurity:是Spring家族一员,是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC和AOP功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

(2)Apache Shiro:一个功能强大且易于使用的Java安全框架,提供了认证、授权、加密和会话管理。


SpringSecurity快速入门

1、创建SpringBoot项目,并导入相关依赖。

<!-- SpringSecurity依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- SpringBoot-Web依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2、创建登录控制器LoginController:

/**
 * @Package: com.duo.controller
 * @Description: 登录控制器
 * @Author 多仔
 */
@Controller
public class LoginController {
    /**
     * @Author: 多仔
     * @Description: 登录页面
     * @return java.lang.String
     **/
    @GetMapping("toLogin")
    public String getLogin() {
        return "login";
    }

    /**
     * @Author: 多仔
     * @Description: 登录请求
     * @return java.lang.String
     **/
    @PostMapping("toLogin")
    public String postLogin() {
        return "redirect:success";
    }

    @GetMapping("success")
    @ResponseBody
    public String getSuccess() {
        return "登录成功";
    }

    @GetMapping("error")
    @ResponseBody
    public String getError() {
        return "登录失败";
    }
}

3、创建前端登录页面login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/toLogin" method="post">
   用户名:<input type="text" name="username" /><br/>
   密码:<input type="password" name="password" /><br/>
    <input type="submit" value="登录" />
</form>
</body>
</html>

4、启动SpringsecurityDemoApplication测试:

(1)导入spring-boot-starter-security启动器后,Spring Security已经生效,默认拦截全部请求,如果用户没有登录,跳转到内置登录页面。

(2)Spring Security默认的username为 user,password打印在控制台中。

(3)原因:SpringSecurity在UserDetailsService中定义了默认的登录逻辑,实际开发中我们需要实现UserDetailsService接口,自定义登录逻辑。


UserDetailsService详解

1、UserDetailsService里面有一个loadUserByUsername()方法,其返回值为UserDetails

public interface UserDetailsService {
    // var1:用户名
    // UsernameNotFoundException:用户名未找到
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

2、UserDetailsService的返回值UserDetails:

public interface UserDetails extends Serializable {
    // 获取所有权限
    Collection<? extends GrantedAuthority> getAuthorities();

    // 获取密码
    String getPassword();

    // 获取用户名
    String getUsername();

    // 判断账户是否过期
    boolean isAccountNonExpired();

    // 判断账户是否锁定
    boolean isAccountNonLocked();

    // 判断凭证(密码)是否过期
    boolean isCredentialsNonExpired();

    // 判断账户是否启用
    boolean isEnabled();
}

3、UserDetails的实现类User:

public class User implements UserDetails, CredentialsContainer {
    // ...

    /**
     * @param username:客户端传递过来的用户名
     * @param password:从数据库中查询出来的密文密码
     * @param authorities:用户具有的权限
     * @return 
     **/
    public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        this(username, password, true, true, true, true, authorities);
    }

    // ...

}

PasswordEncoder详解

1、Spring Security要求容器中必须有PasswordEncoder实例。所以当自定义登录逻辑时要求必须给容器注入PaswordEncoder的bean对象。

2、PasswordEncoder接口:

public interface PasswordEncoder {
    // 把参数按照特定的解析规则进行解析
    String encode(CharSequence var1);

    // 验证从存储中获取的编码密码与编码后提交的原始密码是否匹配
    // var1:需要被解析的密码
    // var2:存储的密码
    boolean matches(CharSequence var1, String var2);

    // 如果解析的密码能够再次进行解析且达到更安全的结果则返回 true
    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

3、在 Spring Security中内置了很多解析器(PasswordEncoder的实现类),常用BCryptPasswordEncoder进行解析。 BCryptPasswordEncoder是对bcrypt强散列方法的具体实现,是基于Hash算法实现的单向加密。

4、PasswordEncoder的使用测试:

@Test
void contextLoads() {
    // 创建密码解析器
    PasswordEncoder pw = new BCryptPasswordEncoder();

    // 对正确的密码进行加密
    String encodePassword = pw.encode("myPassword");
    System.out.println("明文密码加密后为:" + encodePassword);

    // 判断用户输入的密码与正确密码是否匹配
    boolean result = pw.matches("myPassword", encodePassword);
    System.out.println(result);
}

SpringSecurity自定义登录逻辑

1、编写SpringSecurity配置类:

/**
 * @Package: com.duo.config
 * @Description: SpringSecurity配置类
 * @Author 多仔
 */
@Configuration
public class SpringSecurityConfig {

    /**
     * @Author: 多仔
     * @Description: 返回PasswordEncoder实例
     * @return org.springframework.security.crypto.password.PasswordEncoder
     **/
    @Bean
    public PasswordEncoder getPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

2、自定义UserDetailService实现类:

/**
 * @Package: com.duo.service
 * @Description: 自定义登录逻辑
 * @Author 多仔
 */
@Service
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    PasswordEncoder passwordEncoder;

    /**
     * @Author: 多仔
     * @Description: 校验用户是否存在
     * @param s 前端传递的用户名
     * @return org.springframework.security.core.userdetails.UserDetails
     **/
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        // 1、根据用户名,调用Dao查询用户是否存在
        // 模拟数据库查询到的用户名和密文密码(注册时加密)
        String account = "admin";
        String password = passwordEncoder.encode("123456");

        if(!account.equals(s)) {
            System.out.println("该用户不存在!");
            throw new UsernameNotFoundException("该用户不存在!");
        }

        // 2、比较密码
        User user = new User(s, password, AuthorityUtils.commaSeparatedStringToAuthorityList("normal,admin"));

        return user;
    }
}

3、启动SpringsecurityDemoApplication测试。


SpringSecurity自定义登录页面

1、修改SpringSecurity配置类SpringSecurityConfig,继承WebSecurityConfigurerAdapte并重写configure方法。

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                // 自定义登录页面
                .loginPage("/toLogin")
                // 登录请求URL
                .loginProcessingUrl("/toLogin")
                // 登录成功后跳转页面(Post请求)
                .successForwardUrl("/success")
                // 登录失败后跳转页面(Post请求)
                .failureForwardUrl("/error");

        http.authorizeRequests()
                // toLogin不需要被认证
                .antMatchers("/toLogin").permitAll()
                // error不需要被认证
                .antMatchers("/error").permitAll()
                // 所有请求都必须被认证,必须登录后被访问
                .anyRequest().authenticated();

        //关闭csrf防护
        http.csrf().disable();
    }

    // ...
}

2、设置请求用户名、密码的参数名,让后端处理登录逻辑。

// 定义的参数名与表单中对应的输入框的参数名一致
http.formLogin().usernameParameter("username").passwordParameter("password");

3、重启Application,访问/toLogin,即可访问到自定义的登录页面。

4、自定义登录成功/失败处理器:

(1)新建Handler处理器:

/**
 * @Package: com.duo.handler
 * @Description: 登录成功处理
 * @Author 多仔
 */
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private String url = "";

    public MyAuthenticationSuccessHandler() {
    }

    public MyAuthenticationSuccessHandler(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);

        // 跳转到自定义URL
        httpServletResponse.sendRedirect(url);
    }
}

/**
 * @Package: com.duo.handler
 * @Description: 登录失败处理
 * @Author 多仔
 */
public class MyForwardAuthenticationFailureHandler implements AuthenticationFailureHandler {

    private String url = "";

    public MyForwardAuthenticationFailureHandler() {
    }

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

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

(2)修改配置文件:

// 注意:xxxHandler和xxxForwardUrl不能共存

// 登录成功后跳转到https://www.l5v.cn
http.formLogin().successHandler(new MyAuthenticationSuccessHandler("https://www.l5v.cn"));
// 登录失败后跳转到https://www.baidu.com
http.formLogin().failureHandler(new MyForwardAuthenticationFailureHandler("https://www.baidu.com"));
如无特殊说明,本博所有文章均为博主原创。

如若转载,请注明出处:一木林多 - https://www.l5v.cn/archives/322/

评论