API安全(十)-登陆
2021-01-15 05:14
标签:iter data- 测试 控制 一个 Nid 特殊 == hash
在前面,我们把图上常见的安全机制都做了一个简单的实现,但是登陆并没有在图中体现,因为并不是每次调用API的时候都需要登陆;登陆只是一个偶尔发生的事情,并不像图中的机制,每一次API的调用都贯穿在其中。但登陆也是整个安全机制中,重要的一环。 在前面我们实现的HttpBasic认证逻辑中,每次客户端发请求的时候都要把用户的用户名密码通过base64加密传上来,这样有以下缺点: 2.1、不安全,每次请求都要带用户名和密码,增加了用户名密码泄漏的风险 2.2、每一次传上来用户名和密码以后都要去做check,加密算法校验比较消耗系统资源 对于上面的问题,我们可以采用基于token的身份认证,流程如下图;
这样做的好处是:token跟用户名密码是有关联的,但不是直接的关联,从token中没有办法解析出用户名和密码的。不用每次都传用户名和密码,token在服务器端有一个存储,服务器端从客户端拿到token以后,查一下存储中是否存在,就知道用户是否登陆了,不用像之前那样每次请求都要做密码比对。 对于基于token的身份认证实现有很多,对于java来说最常见的就是基于cookie和session的实现,流程如下图;Web浏览器作为客户端,Servlet容器作为服务器端(tomcat等),服务器端的内存作为token存储。
上面的这套逻辑Servlet规范里面都替我们实现好了,我们只需要在代码中执行request.getSession(),就会为我们做上面的生成sessionId,返回set-Cookie这些事情。 优点:提升了客户体验,比客户端保存用户名密码安全;使用起来很方便,Servlet容器都替我们实现好了。 缺点:只针对浏览器可以使用,APP和第三方应用不支持;服务器向浏览器传递cookie容易被劫持;多台服务器要保证session的一致性。 request.getSession()这句代码会根据请求里面cookie的sessionId,在服务器上去找对应的session,如果能找到直接用,如果没找到就会创建一个新的session然后返回回去。针对这样一个逻辑,黑客发明了session固定攻击,如下图
为了防止session固定攻击,我们要保证登陆前和登陆后的session不是同一个。 6.1、登陆方法实现 6.2、Acl权限控制对登陆请求不需要认证 6.3、修改审计功能,从session中获取用户信息 6.4、修改认证功能,同时支持HttpBasic和cookie、session认证 6.5、退出功能 6.6、启动项目进行测试 6.6.1、输入错误的密码进行登陆,在响应头中没有看到Set-Cookie 6.6.2、输入正确的密码进行登陆,响应头中有Set-Cookie,JSESSIONID=79C8ECFDC0AF3EFD82CAE65FAF226E4C 6.6.3、访问获取用户的请求,因为登陆了,请求头Cookie中的JSESSIONID=79C8ECFDC0AF3EFD82CAE65FAF226E4C 所以可以访问 6.6.4、调用退出功能,将原有session失效 6.6.5、访问获取用户的请求,因为没有关闭浏览器,之前的cookie还在,但是调用了退出使对应的session失效了,所以需要使用httpbasic认证 6.6.6、通过httpbasic认证后,可以正常访问,说明两种认证方式都支持。 项目源码:https://github.com/caofanqi/study-security/tree/dev-login API安全(十)-登陆 标签:iter data- 测试 控制 一个 Nid 特殊 == hash 原文地址:https://www.cnblogs.com/caofanqi/p/12241982.html1、登陆
2、之前认证中(HttpBasic)存在的缺陷
3、基于token的身份认证
4、基于cookie和session的实现
5、session固定攻击
6、代码实现
@PostMapping("/login")
public Map
@Override
public Map
/**
* ACL过滤器,这需要审计也是基于Filter实现的
*
* @author caofanqi
* @date 2020/1/29 15:04
*/
@Slf4j
@Order(4)
@Component
@SuppressWarnings("ALL")
public class AclFilter extends OncePerRequestFilter implements InitializingBean {
@Value("${permit.urls}")
private String permitUrls;
private Set
/**
* 获取当前登陆用户
*/
@Bean
public AuditorAware
/**
* HttpBasic 认证
*
* @author caofanqi
* @date 2020/1/21 15:10
*/
@Slf4j
@Order(2)
@Component
@SuppressWarnings("ALL")
public class BasicAuthorizationFilter extends OncePerRequestFilter {
@Resource
private UserRepository userRepository;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
log.info("++++++2、认证++++++");
String authorizationHeader = request.getHeader("Authorization");
if (StringUtils.isNotBlank(authorizationHeader)) {
String token64 = StringUtils.substringAfter(authorizationHeader, "Basic ");
if (StringUtils.isNotBlank(token64)) {
try {
String token = new String(Base64Utils.decodeFromString(token64));
String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(token, ":");
String username = items[0];
String password = items[1];
UserDO user = userRepository.findByUsername(username);
if (user != null && BCrypt.checkpw(password, user.getPassword())) {
// if (user != null && SCryptUtil.check(password,user.getPassword())) {
//认证通过,存放用户信息,对于使用httpBasic认证的,添加特殊标记
request.getSession().setAttribute("user", user.buildUserDTO());
request.getSession().setAttribute("httpBasic", Boolean.TRUE);
}
} catch (Exception e) {
log.info("Basic Authorization Fail!");
}
}
}
//不管认证是否正确,继续往下走,是否可以访问,交给授权处理
filterChain.doFilter(request, response);
//执行完之后,如果是httpBasic方式认证,将session失效
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("httpBasic") != null){
session.invalidate();
}
}
}
@RequestMapping("/logout")
public void logout(HttpServletRequest request){
request.getSession().invalidate();
}