自定义登录页面 http.formLogin().loginPage("/signin.html")
自定义登录成功处理 AuthenticationSuccessHandler
自定义登录失败处理 AuthenticationFailHandler
最终实现的效果是,当访问html页面时会跳转登录页面,并自定义登录成功处理以及自定义登录失败的梳理,而当访问的是非html页面请求时则抛出异常
1. 新建项目
使用idea
中的spring
工具创建并引入springboot
和springsecruity
包
最终的pom.xml
代码如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo-spring-secruity</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-spring-secruity</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
编写SimpleResponse
用于处理输出异常信息的接口
package com.example.demospringsecruity.support;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* 一个简单的响应对象
* @author john
* @date 2020/1/6 - 16:23
*/
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class SimpleResponse {
private Object content;
}
2. 配置自定义登录
通过继承WebSecurityConfigurerAdapter
实现自定义配置
package com.example.demospringsecruity.config;
import com.example.demospringsecruity.handle.MyAuthenticationFailureHandler;
import com.example.demospringsecruity.handle.MyAuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author john
* @date 2020/1/6 - 10:07
*/
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowired
MyAuthenticationFailureHandler myAuthenticationFailureHandler;
//手动将PasswordEncoder注入到ioc容器中
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 表单登录
http.formLogin()
.loginPage("/auth/require") //设置登录路由
.loginProcessingUrl("/auth/form") //设置登录处理url
.successHandler(myAuthenticationSuccessHandler) // 设置自定义成功处理器
.failureHandler(myAuthenticationFailureHandler) // 设置自定义失败处理器
.and()
.authorizeRequests() // 身份认证设置
.antMatchers("/signin.html").permitAll() //该路由不需要身份认账
.antMatchers("/auth/*").permitAll() //该路由不需要身份认账
.anyRequest() //其他的路由均需要身份认证
.authenticated()
.and()
.csrf()
.disable(); //先禁用防止跨站脚本攻击的csrf token
}
}
通过实现UserDetailsService
接口来实现自定义用户认证逻辑
package com.example.demospringsecruity.config;
import lombok.extern.slf4j.Slf4j;
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.Component;
/**
* @author john
* @date 2020/1/6 - 10:32
*/
@Component
@Slf4j
public class MyUserDetailsService implements UserDetailsService {
//这里可以注入mapper或者repository的dao对象来实现数据校验逻辑操作
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("用户名:" + username);
//这里密码应该从数据库中取出,暂时先使用加密生成
String password = passwordEncoder.encode("123456");
return new User(username,
password,
true, // 账户是否可用
true, // 账户是否过期
true, // 密码是否过期
true, //账户是否被锁定
AuthorityUtils.commaSeparatedStringToAuthorityList("admin") //授权集合
);
}
}
编写一个自定义的登录页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h2>标准登录页面</h2>
<h3>表单登录</h3>
<form action="/auth/form" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2">
<button type="submit">登录</button>
</td>
</tr>
</table>
</form>
</body>
</html>
3. 编写访问逻辑
当访问html页面时会跳转登录页面,而当访问的是非html页面请求时则抛出异常
package com.example.demospringsecruity.controller;
import com.example.demospringsecruity.support.SimpleResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author john
* @date 2020/1/6 - 16:03
*/
@RestController
@Slf4j
public class BrowserSecruityController {
private RequestCache requestCache = new HttpSessionRequestCache();
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
//当需要身份认证时,跳转到这---如果是html页面请求跳转到登录页面,否则会被当成ajax异步请求,抛出异常
@RequestMapping("/auth/require")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)
public SimpleResponse requireAuth(HttpServletRequest request, HttpServletResponse response) throws IOException {
//获取前一次的跳转请求
SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest != null) {
String targetUrl = savedRequest.getRedirectUrl();
log.info("引发跳转请求的url是:" + targetUrl);
if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
redirectStrategy.sendRedirect(request, response, "/signin.html");
}
}
return new SimpleResponse("访问的服务需要身份认证,请引导用户到登录页");
}
}
4. 编写自定义登录成功处理
package com.example.demospringsecruity.handle;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 自定义登录成功处理
*
* @author john
* @date 2020/1/6 - 17:20
*/
@Component
@Slf4j
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("登录成功");
//这里处理登录成功后就会json输出authentication里面的数据
response.setContentType("application/json;charset-UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}
5. 编写自定义登录失败处理
package com.example.demospringsecruity.handle;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 自定义登录失败处理
*
* @author john
* @date 2020/1/6 - 18:43
*/
@Component
@Slf4j
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
log.info("登录失败");
//这里处理登录失败后就会输出错误信息
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset-UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(e));
}
}
6. 测试
6.1 测试访问<http://localhost:8081/aa>
6.2 测试访问<http://localhost:8081/index.html>
登录成功
登录失败
7. 代码资源
链接:https://share.weiyun.com/50cUdga 密码:k2r9ie