springboot2 (5) 权限登录

826 阅读7分钟

1. 概念入门

概念: spring-security是spring用于提供声明式安全访问控制解决方案的安全框架:

  • 认证:核心是比对用户的账号密码身份等信息,而验证指的是某些规则的匹配。
  • 授权:为用户赋权或赋角色。

流程: 创建springboot-jar项目 springboot2-security

  • 配置项目依赖:idea直接选择 security/spring security 依赖:
    • spring-boot-starter-security:security核心包。
    • spring-boot-starter-web:spring-web核心包。
    • spring-boot-starter-thymeleaf:thymeleaf核心包,选用。
    • thymeleaf-extras-springsecurity5:thymeleaf和security整合包,选用。
    • spring-security-test:security测试包。
  • 主配添加thymeleaf相关配置:关闭缓存,前后缀,编码,静态资源路径等。
  • 开发测试页面 templates/start.html
  • cli: start.html:发现整个项目资源都被security保护起来:
    • 所有请求重定向到security默认的登录路由 /login 并展示security内置的登录页面。
    • 输入账号 user 和在日志中生成的随机密码,点击登录。
    • 登陆成功后,security默认访问登陆前一次的URL,404时默认寻找 error.html
    • 测试时注意浏览器缓存问题,浏览器完全关闭时视为注销登陆。
  • 自定义账密:在主配中设置自定义账密后,日志将不再生成随机登录密码:
    • spring.security.user.name=yap:自定义security账号。
    • spring.security.user.password=123:自定义security密码。
    • spring.security.user.roles=TEST:自定义账号角色,逗号分隔,不要使用 ROLE_ 前缀。
  • cli: start.html:输入自定义账号密码。

源码: /springboot/

  • res:pom.xml
 <!--spring-boot-starter-security-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<!--spring-boot-starter-thymeleaf-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<!--spring-boot-starter-web-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!--thymeleaf-extras-springsecurity5-->
		<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-springsecurity5</artifactId>
		</dependency>
		<!--spring-boot-devtools-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<!--lombok-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<!--spring-boot-starter-test-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!--spring-security-test-->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
  • web:start.html
<h1>start.html</h1>
  • web:error.html
<h1>404 or auth-fail...</h1>

2. 资源放行

概念: 对于首页,广告页等请求不需要security保护,需要放行:

  • 开发配置类 c.y.s.config.SecurityConfig
    • 标记 @Configuration 声明为配置类。
    • 标记 @EnableWebSecurity 以启用security功能。
  • 配置类重写 o.s.s.c.a.w.c.WebSecurityConfigurerAdapter.configure()
    • http.authorizeRequests():权限请求设置。
    • antMatchers(URL).permitAll():表示放行某些指定规则的请求URL。
    • anyRequest().authenticated():表示其余的任何请求都需要认证。
    • and():用于连接并列关系的配置。
    • formLogin():用于恢复security表单登陆功能。
  • cli: /index.html//ad.html 直接放行,而其他URL仍被security保护。 源码: /springboot/
  • src:c.y.s.config.SecurityConfig
package com.yap.springboot2security.config;
import com.yap.springboot2security.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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;

/**
 * @Author Yap
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/", "/index.html", "/aaa.html").permitAll().anyRequest().authenticated().and().formLogin();

    }
}

  • web:index.html
<h1>index.html</h1>
  • web:ad.html
<h1>ad.html: advertising!</h1>

3. 自定义登陆页面

流程:

  • 开发动作类 c.y.s.controller.UserController
    • loginRouting():用于提供登录页面 /templates/login.html 的路由。
    • mainSuccess():用于提供登录成功页面 /templates/main.html 的路由。
    • SecurityContextHolder.getContext().getAuthentication():登陆后可随时获取security认证信息。
  • configure() 中的 formLogin() 后额外配置,所有URL都建议使用动作方法的路由:
    • loginPage(URL):设置自定义登录页面路由。
    • loginProcessingUrl(URL):设置登录处理路径,命名随意。
    • successForwardUrl(URL):设置登录成功页面的路由,缺省跳转至上次的请求路径。
    • failureForwardUrl(URL):设置登录失败页面的路由,缺省跳转至 loginPage() 指定的路由。
    • permitAll():表示放行以上全部路径或路由。
    • csrf().disable():禁止跨域防护,即允许跨域请求,缺省时自定义表单登录功能失效。
  • 开发登录页面 templates\login.html
    • 引入thymeleaf标签:xmlns:th="https://www.thymeleaf.org"
    • 设计表单,action 的值必须和 loginProcessingUrl() 配置的路径一致。
    • 账号和密码控件建议命名为 usernamepassword
    • ${param.error}:获取账密错误时,security跳转至 loginPage() 指定的路由时附加的 ?error
    • ${param.logout}:获取注销登录时,security跳转至 loginPage() 指定的路由时附加的 ?logout
  • 开发登录成功页面 templates\main.html
    • 引入security标签:xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
    • sec:authorize="isAuthenticated()":仅在认证成功时才会展示标签及标签中的内容。
    • sec:authentication="name":获取认证信息中的账号信息,其余同理。
    • sec:authentication="principal":获取认证信息中的主要信息,如密码(受保护),过期时间等。 源码: /springboot/
  • res:c.y.s.controller.UserController
package com.yap.springboot2security.controller;

import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author yap
 */
@Controller
@RequestMapping("/api/user")
public class UserController {

    @RequestMapping("login-routing")
    public String loginRouting() {
        System.out.println("loginRouting()...");
        return "login";
    }

    @RequestMapping("main-routing")
    public String mainRouting() {
        System.out.println("mainRouting()...");
        System.out.println(SecurityContextHolder.getContext().getAuthentication());
        return "main";
    }
}
  • src:SecurityConfig 修改
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/", "/index.html", "/ad.html").permitAll()
                .anyRequest().authenticated();

        http.csrf().disable();

        http.formLogin()
                .loginPage("/api/user/login-routing")
                .loginProcessingUrl("/login")
                .successForwardUrl("/api/user/main-routing")
                .permitAll();
  • web:login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>custom login page</title>
</head>
<body>
<form action="/login" method="post">
    <label><input type="text" name="username"/></label>
    <label><input type="password" name="password"/></label>
    <button type="submit">登录</button>
</form>
<h4 th:if="${param.error}">账号或密码错误...</h4>
<h4 th:if="${param.logout}">您已注销...</h4>
</body>
</html>
  • web:main.html
<!DOCTYPE html>
<html lang="en" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <title>custom main page</title>
</head>
<body>
<section sec:authorize="isAuthenticated()">
    <h1>main.html</h1>
    <ul>
        <li>username: <strong sec:authentication="name"></strong></li>
        <li>roles: <strong sec:authentication="principal.authorities"></strong></li>
        <li>principal: <strong sec:authentication="principal"></strong></li>
    </ul>
    <!--    <ul>
        <li>登录账号为: <strong sec:authentication="name"></strong></li>
        <li>principal: <strong sec:authentication="principal"></strong></li>
        <li>登录密码为: <strong sec:authentication="principal.password"></strong></li>
        <li>登录角色为: <strong sec:authentication="principal.authorities"></strong></li>
        <li>账号仍可用: <strong sec:authentication="principal.enabled"></strong></li>
        <li>凭证未过期: <strong sec:authentication="principal.credentialsNonExpired"></strong></li>
        <li>账号未过期: <strong sec:authentication="principal.accountNonExpired"></strong></li>
        <li>账号未锁定: <strong sec:authentication="principal.accountNonLocked"></strong></li>
    </ul>-->
</section>
</body>
</html>

4. 配置用户数据

流程: 自定义用户数据,如账密,角色和权限等,此时主配中配置的账密即使保留也会失效:

  • 开发实体类 c.y.s.pojo.Permission/Role/User:集合属性需要给初始值,否则空指针。
  • 开发工具类 c.y.s.util.DataUtil 模拟数据库数据:
    • admin/123ROLE_ADMIN 角色,拥有 insert/select/update/delete 权限。
    • zhaosi/123ROLE_COMM 角色,拥有 select 权限。
  • 开发配置类 c.y.s.c.PasswordEncoderConfig:security规定密码必须进行加密才可使用:
    • security默认的 {noop} 前缀加密方式不安全,建议更换为BCRYPT加密方式:
      • z-res/常用加密算法.md
    • IOC o.s.s.c.p.PasswordEncoder:对其实现类 o.s.s.c.b.BCryptPasswordEncoder 进行实例化。
  • 开发业务类 c.y.s.s.UserDetailsServiceImpl:通过构造注入密码加密类。
  • 重写业务方法 o.s.s.c.u.UserDetailsService.loadUserByUsername()
    • 根据前端传递过来的账号,从数据库中获取对应的 User 实体。
    • passwordEncoder.encode(password):获取密码并对其进行BCRYPT加密。
    • 构建 UserDetails 接口子类 o.s.s.c.u.User 对象并返回,构造需要账密和角色/权限集合。
  • 开发配置类 c.y.s.c.SecurityConfig:用自己的业务类覆盖security默认的业务类:
    • 通过属性注入业务类,构造器注入会产生A构造注入B,B构造注入A的环形注入问题,导致服务器启动崩溃。
    • 通过属性或构造注入密码加密类。
    • 使用 configureGlobal() 注入 AuthenticationManagerBuilder 对象。
    • builder.userDetailsService():使用自定义业务类以覆盖security默认使用的业务类。
    • service.passwordEncoder(passwordEncoder):设置密码加密方式。
  • 分别使用 admin/123zhaosi/123 登陆并查看角色/权限信息,以及观察两个密码123加密后的值是否一样。

源码: /springboot/

  • res:c.y.s.pojo.Permission
package com.yap.springboot2security.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @author yap
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Permission implements Serializable {
    private String keyword;
}
  • res:c.y.s.pojo.Role
package com.yap.springboot2security.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/**
 * @author yap
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Role implements Serializable {
    private String keyword;
    private Set<Permission> permissions = new HashSet<>();
}
  • res:c.y.s.pojo.User
package com.yap.springboot2security.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/**
 * @author yap
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User implements Serializable {
    private String username;
    private String password;
    private Set<Role> roles = new HashSet<>();
}
  • res:c.y.s.util.DataUtil
package com.yap.springboot2security.util;

import com.yap.springboot2security.pojo.User;
import com.yap.springboot2security.pojo.Role;
import com.yap.springboot2security.pojo.Permission;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author yap
 */
public class DataUtil {

    public static Map<String, User> getData() {

        Map<String, User> usersFromDb = new ConcurrentHashMap<>();

        // permission list
        Permission insertPermission = new Permission("insert");
        Permission selectPermission = new Permission("select");
        Permission updatePermission = new Permission("update");
        Permission deletePermission = new Permission("delete");

        // role list
        Role adminRole = new Role();
        adminRole.setKeyword("ROLE_ADMIN");
        adminRole.getPermissions().add(insertPermission);
        adminRole.getPermissions().add(selectPermission);
        adminRole.getPermissions().add(updatePermission);
        adminRole.getPermissions().add(deletePermission);

        Role commRole = new Role();
        commRole.setKeyword("ROLE_COMM");
        commRole.getPermissions().add(selectPermission);

        // user list
        User admin = new User();
        admin.setUsername("admin");
        admin.setPassword("123");
        admin.getRoles().add(adminRole);
        usersFromDb.put(admin.getUsername(), admin);

        User zhaosi = new User();
        zhaosi.setUsername("zhaosi");
        zhaosi.setPassword("123");
        zhaosi.getRoles().add(commRole);
        usersFromDb.put(zhaosi.getUsername(), zhaosi);
        return usersFromDb;
    }
}
  • res:c.y.s.c.PasswordEncoderConfig
package com.yap.springboot2security.config;

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;

/**
 * @author yap
 */
@Configuration
public class PasswordEncoderConfig {

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

  • res:c.y.s.s.UserDetailsServiceImpl
package com.yap.springboot2security.service;

import com.yap.springboot2security.pojo.Permission;
import com.yap.springboot2security.pojo.Role;
import com.yap.springboot2security.pojo.User;
import com.yap.springboot2security.util.DataUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * @author yap
 */
@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    private PasswordEncoder passwordEncoder;

    @Autowired
    public UserDetailsServiceImpl(PasswordEncoder passwordEncoder){
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public UserDetails loadUserByUsername(String username) {

        User user = DataUtil.getData().get(username);

        if (user == null) {
            throw new RuntimeException("username not exist...");
        }

        String password = passwordEncoder.encode(user.getPassword());
        System.out.println("password: " + password);

        return new org.springframework.security.core.userdetails.User(username, password, getAuthoritiesByUser(user));
    }

    private List<GrantedAuthority> getAuthoritiesByUser(User user) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (Role role : user.getRoles()) {
            authorities.add(new SimpleGrantedAuthority(role.getKeyword()));
            for (Permission permission : role.getPermissions()) {
                authorities.add(new SimpleGrantedAuthority(permission.getKeyword()));
            }
        }
        return authorities;
    }
}
  • res:c.y.s.c.SecurityConfig
package com.yap.springboot2security.config;

import com.yap.springboot2security.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.password.PasswordEncoder;

/**
 * @author yap
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsServiceImpl;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder builder) throws Exception {
        builder.userDetailsService(userDetailsServiceImpl).passwordEncoder(passwordEncoder);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/", "/index.html", "/ad.html").permitAll()
                .anyRequest().authenticated();

        http.csrf().disable();

        http.formLogin()
                .loginPage("/api/user/login-routing")
                .loginProcessingUrl("/login")
                .successForwardUrl("/api/user/main-routing")
                .permitAll();
    }
}

常用加密算法

1.对称加密算法

  • 代表:3DES / AES / DES等
  • 举例:将 a 变成 4,将 b 变成 5,于是密码 ab 被加密成了 45
  • 总结:加密规则规律固定,了解规律即可破解,安全性极低。

2.单向HASH加密算法

  • 代表:MD5 / SHA1等
  • 举例:将 a 变为 #,将 b 变成 1,于是密码 ab 被加密成了 #1
  • 总结:HASH算法无规律,无法直接反向破解,但可通过建立彩虹表进行查表破解,如:
    • 我的密码经过MD5加密后变成 Q!#FV!#0G!
    • 你的密码经过MD5加密后也是 Q!#FV!#0G!
    • 于是你自然知道我的密码是什么了,虽然破解费点时间,但也不是绝对的安全。

3.浮动HASH加密算法

  • 代表:BCRYPT:

  • 举例:将 a 变为 k,将 b 变成 6,于是密码 ab 被加密成了 k6

    • 然后再加上一些随机salt(盐份),变成 k6#$
    • 由于每次添加的slat都是随机的,所以彩虹表暴力破解也无能为力。
  • 总结:BCRYPT是HASH算法的升级版,将salt随机混入加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理salt问题,是目前最安全的算法。

5. 路由访问权限

概念: 动作方法可以针对不同的角色/权限进行选择性开放:

  • 开发测试页面 templates/auth.html:添加一些测试超链接。
  • 开发动作类 c.y.s.controller.AuthController:动作方法标记 @PreAuthorize 以设置访问规则:
    • @PreAuthorize("hasRole(A)"):仅A角色可以访问我。
    • @PreAuthorize("hasAnyRole(A, B)"):仅A或B角色可以访问我。
    • @PreAuthorize("hasAuthority(A)"):仅A权限可以访问我。
    • @PreAuthorize("hasAnyAuthority(A, B)"):仅A或B权限可以访问我。
  • 配置类中开启 @PreAuthorize 注解支持:@EnableGlobalMethodSecurity(prePostEnabled=true)
  • 开发页面 auth-success.html,测试成功时跳转,失败时默认自动寻找 error.html,缺省报错。
  • cli: auth.html 测试不同用户的动作方法访问权限。

源码: /springboot/

  • src:c.y.s.controller.AuthController
package com.yap.springboot2security.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author yap
 */
@Controller
@RequestMapping("/api/auth")
public class AuthController {

    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @RequestMapping("admin")
    public String admin() {
        System.out.println("admin()...");
        return "auth-success";
    }

    @PreAuthorize("hasRole('ROLE_COMM')")
    @RequestMapping("comm")
    public String comm() {
        System.out.println("comm()...");
        return "auth-success";
    }

    @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_COMM')")
    @RequestMapping("all")
    public String all() {
        System.out.println("all()...");
        return "auth-success";
    }

    @PreAuthorize("hasAuthority('select')")
    @RequestMapping("select")
    public String select() {
        System.out.println("select()...");
        return "auth-success";
    }

    @PreAuthorize("hasAnyAuthority('delete','update','insert')")
    @RequestMapping("dml")
    public String dml() {
        System.out.println("dml()...");
        return "auth-success";
    }
}
  • web:auth.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>auth.html</h1>
<ul>
    <li><a href="/api/auth/admin">has role: [ROLE_ADMIN]</a></li>
    <li><a href="/api/auth/comm">has role: [ROLE_COMM]</a></li>
    <li><a href="/api/auth/all">has role: [ROLE_ADMIN/ROLE_COMM]</a></li>
    <li><a href="/api/auth/select">has permission: [select]</a></li>
    <li><a href="/api/auth/dml">has permission: [insert/update/delete]</a></li>
</ul>
</body>
</html>

6. 友好注销用户

流程: 浏览器完全退出时表示注销当前登录的用户,但不够友好:

  • 开发页面 logout.html 页面:布局注销按钮。
  • 在动作类 c.y.s.controller.UserController 中添加一个对应注销请求的路由方法,空方法并返回void即可。
  • 在配置类 .formLogin() 后额外配置:
    • .and().logout():定义注销的相关配置。
    • .logoutUrl():必须一致和注销请求URL,以及动作方法路由保持一致。
    • .logoutSuccessUrl():注销成功后的跳转页面,建议使用动作方法路由,缺省调用 loginPage() 指定的页面。
    • .deleteCookies("JSESSIONID"):删除掉cookie中的JSESSIONID,否则连接一直维持,不算注销。
    • .invalidateHttpSession(true):使session失效。
  • 登录再注销,跟踪 F12/Application/Cookie/JSESSIONID 值并测试注销后的账户是否仍可访问项目资源。 源码: /springboot/
  • web:logout.html
<h1>
    <a href="/api/user/logout">注销</a>
</h1>
  • src:c.y.s.controller.UserController
    @RequestMapping("logout")
    public void logout() {}
  • src:c.y.s.config.SecurityConfig
 		.and().logout()
                .logoutUrl("/api/user/logout")
                .deleteCookies("JSESSIONID")
                .invalidateHttpSession(true)
                .permitAll();

7. 重写登录业务

流程: 若希望登陆成功或失败时返回JSON数据而不跳页,则需重写登录成功或失败的处理器类:

  • 开发登陆成功处理器 c.y.s.handler.CustomLoginSuccessHandler
    • 重写 o.s.s.w.a.SavedRequestAwareAuthenticationSuccessHandler.onAuthenticationSuccess()
    • new DefaultRedirectStrategy().sendRedirect(req, resp, URL):可使用重定向跳页。
    • resp.getWriter().write():可使用 repsonse 回写数据。
  • 开发登陆失败处理器 c.y.s.handler.CustomLoginFailureHandler
    • 重写 o.s.s.w.a.SimpleUrlAuthenticationFailureHandler.onAuthenticationFailure()
    • new DefaultRedirectStrategy().sendRedirect(req, resp, URL):可使用重定向跳页。
    • resp.getWriter().write():可使用 repsonse 回写数据。
  • 在配置类中注入这两个处理器类。
  • 将配置类 configure() 中的 successForwardUrl() 替换为:
    • .successHandler(customLoginSuccessHandler):设置自定义登录成功处理器。
    • .failureHandler(customLoginFailureHandler):设置自定义登录失败处理器。 源码: /springboot/
  • src:c.y.s.handler.CustomLoginSuccessHandler
package com.yap.springboot2security.handler;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

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

/**
 * @author yap
 */
@Component
public class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication auth) throws IOException {
        System.out.println("CustomLoginSuccessHandler...");
        resp.setContentType("application/json;charset=UTF-8");
        resp.getWriter().write("login success!");
    }
}
  • src:c.y.s.handler.CustomLoginFailureHandler
package com.yap.springboot2security.handler;

import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;

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

/**
 * @author yap
 */
@Component
public class CustomLoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException exception) throws IOException {
        System.out.println("CustomLoginFailureHandler...");
        resp.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        resp.setContentType("application/json;charset=UTF-8");
        resp.getWriter().write("login fail!");
    }
}
  • src:c.y.s.config.SecurityConfig
    @Autowired
    private CustomLoginSuccessHandler customLoginSuccessHandler;

    @Autowired
    private CustomLoginFailureHandler customLoginFailureHandler;



http.formLogin()
                .loginPage("/api/user/login-routing")
                .loginProcessingUrl("/login")
                //.successForwardUrl("/api/user/main-routing")
                .successHandler(customLoginSuccessHandler)
                .failureHandler(customLoginFailureHandler)
                .permitAll()
                .and().logout()
                .logoutUrl("/api/user/logout")
                .deleteCookies("JSESSIONID")
                .invalidateHttpSession(true)
                .permitAll();