spring-security【2022-3-18更新】

巫化
2023-12-01

【2022-3-18更新】

  1. 文档结构目录;
  2. 更新笔记。

更新整理  Git,或 点击这里

导包

<!--   spring security 包含下面两个注释掉的包   -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--        <dependency>-->
<!--            <groupId>org.springframework.security</groupId>-->
<!--            <artifactId>spring-security-web</artifactId>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.security</groupId>-->
<!--            <artifactId>spring-security-config</artifactId>-->
<!--        </dependency>-->
  • WebSecurityConfigurerAdapter:自定义 Security 策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecuryti模式

Authentication - 认证
Authorization - 授权

  • Controller.java
package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MyController {

    @RequestMapping("/toMain")
    public String toLogin(){
        return "redirect:main.html";
    }

    @RequestMapping("/toError")
    public String Error(){
        return "redirect:error.html";
    }
}

本质上主要是使用了AOP 和 拦截器!

  • SecurityConfiguration.java
package com.example.demo.config;

import com.example.demo.hanlder.MyAccessDeniedHandler;
import com.example.demo.hanlder.MyAuthenticationFailureHandler;
import com.example.demo.hanlder.MyAuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;

    // 下面自定义登录逻辑时需要用到
    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 请求授权的规则
        http.authorizeRequests()
                .antMatchers("/login.html").permitAll() // 任何权限都可以访问
                .antMatchers("/error.html").permitAll()

                // 权限判断 UserDetailsServiceImpl
                .antMatchers("/main.html").hasAnyAuthority("admina", "user")

                // 还可以通过ip地址来开放权限
//                .antMatchers("/main.html").hasIpAddress("****")

                // 角色判断 UserDetailsServiceImpl  或者 下面的认证里面设置
//                .antMatchers("/main.html").hasRole("abc")

                // 使用正则表达式放行访问文件
//                .regexMatchers()

                // 放行使用 get 方式访问的资源。
//                .regexMatchers(HttpMethod.GET,"get").permitAll()

                // 主要适用于 servletPath 路径请求的方式
                // servletPath - 在 .properties 中进行配置的项目路径。 -- 一般使用 antMatchers 已经足够了
//                .mvcMatchers("/demo").servletPath("/xxx").permitAll()

                .anyRequest().authenticated(); // 所有请求必须被认证 (必须登录之后被访问)


        /**
         * <p>
         * 没有权限默认事件 - 比如登录界面
         * </p>
         *
         * loginPage - 自定义登录界面
         * loginProcessingUrl - 当发现此参数时认为是登录;就是实际登录访问的接口地址,必须和表单提交的地址一样 然后执行 UserDetailsServiceImpl
         * usernameParameter - 设置表单传参的属性
         * passwordParameter - 设置表单传参的属性
         * successForwardUrl - 登录成功跳转页面。 post 请求。-- 需要写 MyController
         * failureForwardUrl - 登录失败跳转界面。post 请求。-- 需要写 MyController
         * successHandler - 登录成功后处理器,不能和 successForwardUrl 共存
         * failureHandler - 登录失败后处理器,不能和 failureForwardUrl 共存
         */
        http.formLogin()
                .loginPage("/login.html")
                .usernameParameter("user")
                .passwordParameter("pwd")
                .loginProcessingUrl("/loogin")
//                .successForwardUrl("/toMain")
//                .failureForwardUrl("/toError");
                .successHandler(new MyAuthenticationSuccessHandler("/main.html"))
                .failureHandler(new MyAuthenticationFailureHandler("/error.html"));

        // 防跨站攻击 关闭csrf功能 - 登录失败的原因
        http.csrf().disable();

        // 开启注销功能 - 删除cookie、之类的... 一般跳到首页或者登录界面
        // logoutSuccessUrl - 注销成功跳转url
//        http.logout().logoutSuccessUrl("/login");

        // 开启记住我功能,cookie  存活时间 2周。 自定义接收参数
//        http.rememberMe().rememberMeParameter("remenber");

        // 异常处理 - 403 权限不够
        http.exceptionHandling()
                .accessDeniedHandler(myAccessDeniedHandler);
    }

    // 认证
//    @Override
//    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//
//        // 密码加密与匹配密码
//        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
//        String result = encoder.encode("myPassword");
//        System.out.println(encoder.matches("myPassword", result));
//
//        // 这些用户数据应该从数据库中获取
//        /*
//            password 这部分,必须要进行加密方式处理。 passwordEncoder(new BCryptPasswordEncoder())
//            否则后台报错 There is no PasswordEncoder mapped for the id "null"
//        */
//        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
//                .withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("admin", "user")
//                .and()
//                .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("admin");
//    }


}


  • 其中,如果要自定义一个登录逻辑,需要实现一个 UserDetailsService 这个接口
  • UserDetailsServiceImpl.java
package com.example.demo;

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;

/**
 * <p>
 * 自定义登录逻辑
 * </p>
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    PasswordEncoder passwordEncoder;

    // 模拟的后端数据
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 这个 username 是表单中的 username  ->  可以通过配置更改为 pwd 等其他
        System.out.println("自定义登录逻辑,前端传参用户名:"+username);

//        User user = userService.findByUserName(username);
//        System.out.println("自定义登录逻辑,获取到的用户:"+user);

        // 1.查询数据库判断用户是否存在,不存在则抛异常 UsernameNotFoundException
        if ("user".equals(username)) {
            // 2.存在用户 -> 获取密码(注册时已经加密过的密码,进行解析) 或者 直接把密码放入构造方法

            String password = passwordEncoder.encode("123");
            // admin 是权限  ROLE_abc 是角色
            return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin, ROLE_abc"));
        } else {
            throw new UsernameNotFoundException("用户不存在");
        }
    }
}

另外还有自定义登录认证成功、失败方法

  • 成功
package com.example.demo.hanlder;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
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 MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private String url;

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

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        System.out.println("自定义登录认证成功处理");
        User user = (User) authentication.getPrincipal();

        System.out.println("登录成功,用户信息:" + user.toString());

        response.sendRedirect(url);
    }
}

  • 失败
package com.example.demo.hanlder;

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 MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

    private String url;

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

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        System.out.println("自定义登录认证失败处理");
        response.sendRedirect(url);
    }
}

自定义权限不够 403 异常处理方法

package com.example.demo.hanlder;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

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

/**
 * <p>
 * 自定义处理 403 方案
 * </p>
 */
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        System.out.println("自定义权限不够,403处理方案");
        // 响应码
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.setHeader("Content-Type", "application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write("模拟权限不够界面:权限不够!");
        writer.flush();
    }
}

模拟前端界面

  • error.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
登录错误界面

</body>
</html>
  • login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>

    <link rel="stylesheet" type="text/css" href="login.css"/>
    <script type="text/javascript" src="login.js"></script>
</head>

<body>
<div id="login_frame">

    <form method="post" action="/loogin">

        <p><label >用户名</label><input type="text" name="user" /></p>
        <p><label >密码</label><input type="text" name="pwd"/></p>
        <button type="submit">登录</button>
    </form>
</div>

</body>
</html>
  • main.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
主界面main

</body>
</html>
 类似资料: