单点登录
单点登录
- SpringMVC的拦截器通常用于处理请求和响应的预处理和后处理, 如日志记录,权限检查,跨域请求处理等, 它可以在请求到达控制器之前或响应返回客户端之前执行自定义逻辑。
- 优点:轻量级,适用于基本的请求处理逻辑。易于配置和定制;
- Spring Security是一个强大的安全框架,专注于身份验证和授权。它的拦截器用于保护应用程序的安全性,可以进行用户身份验证、授权检查、会话管理、CSRF 防护等。适用于敏感数据和操作的保护。
- 优点:提供了全面的安全功能,支持多种身份验证和授权机制。可用于构建复杂的安全策略;
用户登录可以使用Spring MVC和Spring Security的组合。Spring MVC用于处理Web请求和页面渲染,而Spring Security用于管理用户身份验证和授权, 而单点登录是登录功能的一种升级…
JWT—JSON Web Token
跨域身份解决方案, 以前使用会话共享, 如果要服务端实现集群, 会很复杂, 使用Token开销会小很多
Single Sign On是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部分;
-
同域名共享Cookie
统一域名下,各系统使用的技术要相同,Cookie本身不安全跨域:只要网络协议,IP地址,端口号有一个不同就是跨域
-
SSO认证授权登录
多平台必须要有一个唯一的账号- 用户访问app系统,app系统是需要登录的,但用户现在没有登录。
- 跳转到CAS server,即SSO登录系统,以后图中的CAS Server我们统一叫做SSO系统。 SSO系统也没有登录,弹出用户登录页。
- 用户填写用户名、密码,SSO系统进行认证后,将登录状态写入SSO的session,浏览器(Browser)中写入SSO域下的Cookie。
- SSO系统登录完成后会生成一个ST(Service Ticket),然后跳转到app系统,同时将ST作为参数传递给app系统。
- app系统拿到ST后,从后台向SSO发送请求,验证ST是否有效。
- 验证通过后,app系统将登录状态写入session并设置app域下的Cookie。
至此,跨域单点登录就完成了。以后我们再访问app系统时,app就是登录的。接下来,我们再看看访问app2系统时的流程。
- 用户访问app2系统,app2系统没有登录,跳转到SSO。
- 由于SSO已经登录了,不需要重新登录认证。
- SSO生成ST,浏览器跳转到app2系统,并将ST作为参数传递给app2。
- app2拿到ST,后台访问SSO,验证ST是否有效。
- 验证成功后,app2将登录状态写入session,并在app2域下写入Cookie。
这样,app2系统不需要走登录流程,就已经是登录了。SSO,app和app2在不同的域,它们之间的session不共享也是没问题的。
SpringSecurity
遇到的问题
1. 遇到重定向次数过多,提示清除Cookie,检查是否拦截所有请求,导致页面不断刷新;
2. 遇到登录提交后没有反应,
+ 检查有没有走自定义逻辑登录
+ 检查是否设置为POST请求,通过GET请求会传递敏感信息(GET请求将数据附加在URL中)是不安全的,登录通常要使用POST请求,因为这些信息会在浏览器历史记录,服务器日志等地方留下记录
3. 默认只能是POST提交请求,登录失败也是POST请求且默认username,password,可以自定义;内部自动去两端空格;
SecurityConfig配置类:
package cn.tjw.config;
import cn.tjw.handler.MyAccessDeniedHandler;
import cn.tjw.handler.MyAuthenticationFailurehandler;
import cn.tjw.handler.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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* {@code @Author:} tianjw
* {@code @Date:} 2023/9/9 10:16
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyAccessDeniedHandler myAccessDeniedHandler;
protected void configure(HttpSecurity http) throws Exception{
http.formLogin()
// 自定义登录页面
.loginPage("/login.html")
// 必须和表单提交的action对应,会去执行自定义逻辑
.loginProcessingUrl("/login")
// 登录成功后跳转的页面,POST请求
// .successForwardUrl("/toMain")
.successHandler(new MyAuthenticationSuccesshandler("https://www.baidu.com/"))
// .failureForwardUrl("/toError");
// 可以直接重定向到页面error.html,也可以跳到路径映射方法/toError,但是都要检查是否在授权功能被排除拦截
.failureHandler(new MyAuthenticationFailurehandler("/toError"));
// 授权
// 从上往下执行,放行的需要放在前面
http.authorizeRequests()
// 放行登录页面,error页放行
// .permitAll()是允许所有这种,也可以设置为denyAll()
.antMatchers("/toError").permitAll()
.antMatchers("/login.html").permitAll()
.antMatchers("/error.html").permitAll()
// 匹配放行,放行静态资源等
.antMatchers("/css/**","/js/**","/images/**").permitAll()
.antMatchers("/**/*.png").permitAll()
// 正则匹配 []是转义,放行所有.png的资源
.regexMatchers(".[.]png]").permitAll()
// MVC匹配
.mvcMatchers("/demo").servletPath("/Security").permitAll()
/**
* 权限控制
*/
//权限控制 区分大小写 hasAnyAuthority多选,有其中之一权限即可
.antMatchers("/main.html").hasAnyAuthority("admin","admiN")
// 基于角色控制 在UserDetailServiceImpl给角色添加权限,只有abc能访问这个页面
.antMatchers("/main.html").hasRole("abc")
// 基于IP控制 访问这个main.html页面只能这个IP访问
.antMatchers("/main.html").hasIpAddress("127.0.0.1")
// 所有请求都必须认证才能访问,必须登录认证
// 拦截所有,只会排除上面的
.anyRequest().authenticated();
// 异常处理
http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);
// 关闭srf防护
http.csrf().disable();
}
// 这样相当于把BCryptPasswordEncoder对象注入Spring容器中
@Bean
public PasswordEncoder getPw(){
return new BCryptPasswordEncoder();
}
}
UserDetailServiceImpl类:
package cn.tjw.service;
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;
/**
* {@code @Author:} tianjw
* {@code @Date:} 2023/9/9 09:59
*/
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("执行自定义登录逻辑");
if (!"admin".equals(username)){
throw new UsernameNotFoundException("用户名不存在");
}
// 比较密码
String password = passwordEncoder.encode("123");
return new User(username, password,
// 拥有admin和normal级别的权限 ROLE_开头给用户权限
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc"));
}
}
自定义MyAuthenticationSuccesshandler:
package cn.tjw.handler;
import org.springframework.security.core.Authentication;
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;
/**
* {@code @Author:} tianjw
* {@code @Date:} 2023/9/9 12:56
* AuthenticationSuccessHandler底层实现是请求转发
* 实现自己的成功处理器,重定向到别的域
*/
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(request.getRemoteAddr());
response.sendRedirect(url);
}
}
自定义MyAuthenticationFailurehandler类:
package cn.tjw.handler;
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;
/**
* {@code @Author:} tianjw
* {@code @Date:} 2023/9/9 13:16
*/
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 {
response.sendRedirect(url);
}
}
自定义MyAccessDeniedHandler:
package cn.tjw.handler;
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;
/**
* {@code @Author:} tianjw
* {@code @Date:} 2023/9/9 14:38
* 自定义异常页面 403权限不足
*/
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setHeader("Content-Type", "application/json;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write("{\"status\":\"error\",\"msg:\":\"权限不足,请联系管理员\"}");
// 强制刷新缓冲区,确保数据写入文件
writer.flush();
writer.close();
}
}
注解开发
access表达式
先在启动类开启@EnableGlobalMethodSecurity(securedEnable = true)
@Secured注解
@Secured专门用于判断是否具有角色的,能写在方法或类上,参数要以ROLE_开头
- 基于角色判断
@PreAuthorize/@PostAuthorize
@PreAuthorize表示访问方法或类在执行之前先判断权限,大多数情况使用这个注解
@PostAuthorize表示在方法或类执行结束后判断权限,较少使用

浙公网安备 33010602011771号