前面说了用户的配置,并没有springSecurity的功能配置。这个httpSecurity就是配置SpringSecurity功能的。
还是在WebSecurityConfig.class
这个类中进行HttpSecurity配置。当我们什么都不配置的时候,HttpSecurity有自己默认的配置。官方文档给出了,我们拿过来在 默认配置基础上修改:
@Configuration
// mvc项目需要手动加这个注解,Springboot项目在我们引入security时自动加了
//@EnableWebSecurity
public class WebSecurityConfig {
/**
* 加密方法过时了,我们自己写一个
* 官方使用的是DelegatingPasswordEncoder(代理密码加密),默认也是用的BCryptPasswordEncoder,但是会有前缀(加密方式),我们直接用BCryptPasswordEncoder
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 基于内存的用户认证
* 注意 有自定义配置后,application.yml文件中配置的账号密码就失效了
*/
// @Bean
// UserDetailsService userDetailsService() {
// // 内存用户管理器
// InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// manager.createUser(
// // 创建UserDetails对象,管理账号、密码、角色、权限等
// User.withUsername("user")
// .password(passwordEncoder().encode("password") )
// .roles("USER")
// .build());
//
// return manager;
// }
/**
* 基于数据源的用户认证
*/
@Bean
UserDetailsService userDetailsService() {
MyUserDetailService userDetail = new MyUserDetailService();
return userDetail;
}
/**
* HttpSecurity功能设置,根据功能,得到自己的过滤器链
*/
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 权限设置
.authorizeHttpRequests(authorize ->
authorize
// 任何资源请求
.anyRequest()
// 都需要登陆
.authenticated()
)
// 登录页、登出页使用security提供的表单模式
.formLogin(Customizer.withDefaults())
// 使用账号密码这种登陆方式,有formLogin这个可以不用
// .httpBasic(Customizer.withDefaults())
;
return http.build();
}
}
1. 自定义登陆页面
添加LoginController跳转到login.html
1.1 LoginController
@Controller
public class LoginController {
@GetMapping("/tologin")
public String login() {
return "login";
}
}
1.2 login.html
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head>
<title>登录</title>
</head>
<body>
<h1>登录</h1>
<div th:if="${param.error}">
错误的用户名和密码.</div>
<!--method必须为"post"-->
<!--th:action="@{/tologin}" ,
使用动态参数,表单中会自动生成_csrf隐藏字段,用于防止csrf攻击
tologin: 和登录页面保持一致即可,SpringSecurity自动进行登录认证-->
<form th:action="@{/tologin}" method="post">
<div>
<!--name必须为"username"-->
<input type="text" name="username" placeholder="用户名"/>
</div>
<div>
<!--name必须为"password"-->
<input type="password" name="password" placeholder="密码"/>
</div>
<input type="submit" value="登录" />
</form>
</body>
</html>
因为用到了thymeleaf,所以放 templates目录下
结构
1.3 WebSecurityConfig配置
在httpSecurity中配置自定义登陆页面
@Configuration
// mvc项目需要手动加这个注解,Springboot项目在我们引入security时自动加了
//@EnableWebSecurity
public class WebSecurityConfig {
/**
* 默认密码加密过时了,说不安全
* 我们换一个安全的
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 基于内存的用户认证
* 注意 有自定义配置后,application.yml文件中配置的账号密码就失效了
*/
// @Bean
// UserDetailsService userDetailsService() {
// // 内存用户管理器
// InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// manager.createUser(
// // 创建UserDetails对象,管理账号、密码、角色、权限等
// User.withUsername("user")
// .password(passwordEncoder().encode("password") )
// .roles("USER")
// .build());
//
// return manager;
// }
/**
* 基于数据源的用户认证
*/
@Bean
UserDetailsService userDetailsService() {
MyUserDetailService userDetail = new MyUserDetailService();
return userDetail;
}
/**
* HttpSecurity功能设置,根据功能,得到自己的过滤器链
*/
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 权限设置
.authorizeHttpRequests(authorize ->
authorize
// 任何资源请求
.anyRequest()
// 都需要登陆
.authenticated()
)
// 登录页、登出页使用security提供的表单模式
// .formLogin(Customizer.withDefaults())
// 自定义登陆页面
.formLogin(form -> {
form.loginPage("/tologin").permitAll()
//自定义表单用户名参数,默认是username
.usernameParameter("username")
//自定义表单密码参数,默认是password
.passwordParameter("password")
//登录失败的返回地址,加了参数,是因为有这个参数,页面会有提示
.failureUrl("/tologin?error")
;
})
// 使用账号密码这种登陆方式,有formLogin这个可以不用
// .httpBasic(Customizer.withDefaults())
;
// 关闭post请求的 csrf
http.csrf(csrf -> csrf.disable());
return http.build();
}
}
2. 前后端分离,自定义登陆返回json数据
验证用户流程如下:
usernamepasswordauthenticationfilter
有2个类需要重写:
- 登录成功后调用:AuthenticationSuccessHandler
- 登录失败后调用:AuthenticationFailureHandler
2.1 MyAuthenticationSuccessHandler
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private final HttpMessageConverter<Object> httpResponseConverter = new MappingJackson2HttpMessageConverter();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//获取用户身份信息
Object principal = authentication.getPrincipal();
//创建结果对象
Map<String, Object> res = new HashMap<String, Object>();
res.put("code", "1000");
res.put("message", "登录成功");
res.put("data", principal);
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
httpResponseConverter.write(res, null, httpResponse);
}
}
2.2 MyAuthenticationFailureHandler
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
private final HttpMessageConverter<Object> httpResponseConverter = new MappingJackson2HttpMessageConverter();
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
//获取错误信息
String localizedMessage = exception.getLocalizedMessage();
//创建结果对象
Map<String, Object> res = new HashMap<String, Object>();
res.put("code", "4000");
res.put("message", localizedMessage);
//返回响应
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
httpResponseConverter.write(res, null, httpResponse);
}
}
2.3 配置httpSecurity
@Configuration
// mvc项目需要手动加这个注解,Springboot项目在我们引入security时自动加了
//@EnableWebSecurity
public class WebSecurityConfig {
/**
* 默认密码加密过时了,说不安全
* 我们换一个安全的
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 基于内存的用户认证
* 注意 有自定义配置后,application.yml文件中配置的账号密码就失效了
*/
// @Bean
// UserDetailsService userDetailsService() {
// // 内存用户管理器
// InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// manager.createUser(
// // 创建UserDetails对象,管理账号、密码、角色、权限等
// User.withUsername("user")
// .password(passwordEncoder().encode("password") )
// .roles("USER")
// .build());
//
// return manager;
// }
/**
* 基于数据源的用户认证
*/
@Bean
UserDetailsService userDetailsService() {
MyUserDetailService userDetail = new MyUserDetailService();
return userDetail;
}
/**
* HttpSecurity功能设置,根据功能,得到自己的过滤器链
*/
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 权限设置
.authorizeHttpRequests(authorize ->
authorize
// /test路径 放行
.requestMatchers("/test/**").permitAll()
// /test1路径需要test1权限
.requestMatchers("/test1/**").hasAuthority("test1")
// 其他任何资源请求
.anyRequest()
// 都需要登陆
.authenticated()
)
// 登录页、登出页使用security提供的表单模式
// .formLogin(Customizer.withDefaults())
// 自定义登陆页面
.formLogin(form -> {
form.loginPage("/tologin").permitAll()
//自定义表单用户名参数,默认是username
.usernameParameter("username")
//自定义表单密码参数,默认是password
.passwordParameter("password")
//登录失败的返回地址,加了参数,是因为有这个参数,页面会有提示
// .failureUrl("/tologin?error")
// 登陆成功处理
.successHandler(new MyAuthenticationSuccessHandler())
// 登录失败处理
.failureHandler(new MyAuthenticationFailureHandler())
;
})
// 使用账号密码这种登陆方式,有formLogin这个可以不用
// .httpBasic(Customizer.withDefaults())
;
// 关闭post请求的 csrf
http.csrf(csrf -> csrf.disable());
return http.build();
}
}
3 自定义注销返回
3.1 MyLogoutSuccessHandler
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
private final HttpMessageConverter<Object> httpResponseConverter = new MappingJackson2HttpMessageConverter();
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
//创建结果对象
Map<String, Object> res = new HashMap<String, Object>();
res.put("code", "1000");
res.put("message", "注销成功");
//返回响应
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
httpResponseConverter.write(res, null, httpResponse);
}
}
3.2 httpSecurity
@Configuration
// mvc项目需要手动加这个注解,Springboot项目在我们引入security时自动加了
//@EnableWebSecurity
public class WebSecurityConfig {
/**
* 默认密码加密过时了,说不安全
* 我们换一个安全的
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 基于内存的用户认证
* 注意 有自定义配置后,application.yml文件中配置的账号密码就失效了
*/
// @Bean
// UserDetailsService userDetailsService() {
// // 内存用户管理器
// InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// manager.createUser(
// // 创建UserDetails对象,管理账号、密码、角色、权限等
// User.withUsername("user")
// .password(passwordEncoder().encode("password") )
// .roles("USER")
// .build());
//
// return manager;
// }
/**
* 基于数据源的用户认证
*/
@Bean
UserDetailsService userDetailsService() {
MyUserDetailService userDetail = new MyUserDetailService();
return userDetail;
}
/**
* HttpSecurity功能设置,根据功能,得到自己的过滤器链
*/
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 权限设置
.authorizeHttpRequests(authorize ->
authorize
// /test路径 放行
.requestMatchers("/test/**").permitAll()
// /test1路径需要test1权限
.requestMatchers("/test1/**").hasAuthority("test1")
// 其他任何资源请求
.anyRequest()
// 都需要登陆
.authenticated()
)
// 登录页、登出页使用security提供的表单模式
// .formLogin(Customizer.withDefaults())
// 自定义登陆页面
.formLogin(form -> {
form.loginPage("/tologin").permitAll()
//自定义表单用户名参数,默认是username
.usernameParameter("username")
//自定义表单密码参数,默认是password
.passwordParameter("password")
//登录失败的返回地址,加了参数,是因为有这个参数,页面会有提示
// .failureUrl("/tologin?error")
// 登陆成功处理
.successHandler(new MyAuthenticationSuccessHandler())
// 登录失败处理
.failureHandler(new MyAuthenticationFailureHandler())
;
})
// 使用账号密码这种登陆方式,有formLogin这个可以不用
// .httpBasic(Customizer.withDefaults())
;
// 关闭post请求的 csrf
http.csrf(csrf -> csrf.disable());
// 注销配置
http.logout(logout -> logout.logoutSuccessHandler(new MyLogoutSuccessHandler()));
return http.build();
}
}
4. 自定义无权限请求返回
4.1 MyAuthenticationEntryPoint
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final HttpMessageConverter<Object> httpResponseConverter = new MappingJackson2HttpMessageConverter();
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
//获取错误信息
//String localizedMessage = authException.getLocalizedMessage();
//创建结果对象
//创建结果对象
Map<String, Object> res = new HashMap<String, Object>();
res.put("code", "4000");
res.put("message", "需要登录");
//返回响应
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
httpResponseConverter.write(res, null, httpResponse);
}
}
4.2 httpSecurity
// 请求未认证的接口
http.exceptionHandling(exception -> exception.authenticationEntryPoint(new MyAuthenticationEntryPoint()));
5. 配置账号只允许登陆一次
当账号2次登录时,第一次登陆会失效,再操作第一次登陆的账号时,会返回我们自定义信息。
5.1 MySessionInformationExpiredStrategy
public class MySessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
private final HttpMessageConverter<Object> httpResponseConverter = new MappingJackson2HttpMessageConverter();
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
//创建结果对象
Map<String, Object> res = new HashMap<String, Object>();
res.put("code", "4000");
res.put("message", "该账号已从其他设备登录");
//返回响应
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(event.getResponse());
httpResponseConverter.write(res, null, httpResponse);
}
}
5.2 httpSecurity
// 账号只登陆一次
http.sessionManagement(session -> session.maximumSessions(1).expiredSessionStrategy(new MySessionInformationExpiredStrategy()));
SpringSecurity配置基本上好了。最后结构和配置如下:
结构
配置:
@Configuration
// mvc项目需要手动加这个注解,Springboot项目在我们引入security时自动加了
//@EnableWebSecurity
public class WebSecurityConfig {
/**
* 默认密码加密过时了,说不安全
* 我们换一个安全的
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 基于内存的用户认证
* 注意 有自定义配置后,application.yml文件中配置的账号密码就失效了
*/
// @Bean
// UserDetailsService userDetailsService() {
// // 内存用户管理器
// InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// manager.createUser(
// // 创建UserDetails对象,管理账号、密码、角色、权限等
// User.withUsername("user")
// .password(passwordEncoder().encode("password") )
// .roles("USER")
// .build());
//
// return manager;
// }
/**
* 基于数据源的用户认证
*/
@Bean
UserDetailsService userDetailsService() {
MyUserDetailService userDetail = new MyUserDetailService();
return userDetail;
}
/**
* HttpSecurity功能设置,根据功能,得到自己的过滤器链
*/
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 权限设置
.authorizeHttpRequests(authorize ->
authorize
// /test路径 放行
.requestMatchers("/test/**").permitAll()
// /test1路径需要test1权限
.requestMatchers("/test1/**").hasAuthority("test1")
// 其他任何资源请求
.anyRequest()
// 都需要登陆
.authenticated()
)
// 登录页、登出页使用security提供的表单模式
// .formLogin(Customizer.withDefaults())
// 自定义登陆页面
.formLogin(form -> {
form.loginPage("/tologin").permitAll()
//自定义表单用户名参数,默认是username
.usernameParameter("username")
//自定义表单密码参数,默认是password
.passwordParameter("password")
//登录失败的返回地址,加了参数,是因为有这个参数,页面会有提示
// .failureUrl("/tologin?error")
// 登陆成功处理
.successHandler(new MyAuthenticationSuccessHandler())
// 登录失败处理
.failureHandler(new MyAuthenticationFailureHandler())
;
})
// 使用账号密码这种登陆方式,有formLogin这个可以不用
// .httpBasic(Customizer.withDefaults())
;
// 关闭post请求的 csrf
http.csrf(csrf -> csrf.disable());
// 注销配置
http.logout(logout -> logout.logoutSuccessHandler(new MyLogoutSuccessHandler()));
// 错误处理
// 请求未认证的接口
http.exceptionHandling(exception -> exception.authenticationEntryPoint(new MyAuthenticationEntryPoint()));
// 账号只登陆一次
http.sessionManagement(session -> session.maximumSessions(1).expiredSessionStrategy(new MySessionInformationExpiredStrategy()));
return http.build();
}
}