diff --git a/servlet-core/src/main/java/tech/smartboot/servlet/SmartHttpServletRequest.java b/servlet-core/src/main/java/tech/smartboot/servlet/SmartHttpServletRequest.java index c1ad865..5fb4bbf 100644 --- a/servlet-core/src/main/java/tech/smartboot/servlet/SmartHttpServletRequest.java +++ b/servlet-core/src/main/java/tech/smartboot/servlet/SmartHttpServletRequest.java @@ -14,6 +14,7 @@ import org.smartboot.socket.util.Attachment; import tech.smartboot.servlet.conf.ServletInfo; import tech.smartboot.servlet.conf.ServletMappingInfo; +import tech.smartboot.servlet.plugins.security.LoginAccount; /** * @author 三刀 @@ -42,4 +43,6 @@ public interface SmartHttpServletRequest extends HttpServletRequest { void setAsyncSupported(boolean supported); + void setLoginAccount(LoginAccount loginAccount); + } diff --git a/servlet-core/src/main/java/tech/smartboot/servlet/WebXmlParseEngine.java b/servlet-core/src/main/java/tech/smartboot/servlet/WebXmlParseEngine.java index 7bb1180..b2d0b81 100644 --- a/servlet-core/src/main/java/tech/smartboot/servlet/WebXmlParseEngine.java +++ b/servlet-core/src/main/java/tech/smartboot/servlet/WebXmlParseEngine.java @@ -368,6 +368,7 @@ private void parseSecurityConstraint(WebAppInfo webAppInfo, Element parentElemen Node webResourceCollection = Objects.requireNonNull(getChildNode(node, "web-resource-collection")); // Map> data = getNodeValues(webResourceCollection, Arrays.asList("web-resource-name", "url-pattern", "http-method")); securityConstraint.getHttpMethods().addAll(getNodeValues(webResourceCollection, "http-method")); + securityConstraint.getHttpMethodOmissions().addAll(getNodeValues(webResourceCollection, "http-method-omission")); getNodeValues(webResourceCollection, "url-pattern").forEach(urlPattern -> securityConstraint.getUrlPatterns().add(new UrlPattern(urlPattern))); // securityConstraint.getResourceNames().addAll(getNodeValues(webResourceCollection, "web-resource-name")); diff --git a/servlet-core/src/main/java/tech/smartboot/servlet/conf/SecurityConstraint.java b/servlet-core/src/main/java/tech/smartboot/servlet/conf/SecurityConstraint.java index 3114bf2..7898e9f 100644 --- a/servlet-core/src/main/java/tech/smartboot/servlet/conf/SecurityConstraint.java +++ b/servlet-core/src/main/java/tech/smartboot/servlet/conf/SecurityConstraint.java @@ -21,6 +21,7 @@ public class SecurityConstraint { // private final List resourceNames = new ArrayList<>(); private final List urlPatterns = new ArrayList<>(); private final List httpMethods = new ArrayList<>(); + private final List httpMethodOmissions = new ArrayList(); private List roleNames; @@ -32,6 +33,10 @@ public List getHttpMethods() { return httpMethods; } + public List getHttpMethodOmissions() { + return httpMethodOmissions; + } + public List getRoleNames() { return roleNames; } diff --git a/servlet-core/src/main/java/tech/smartboot/servlet/handler/SecurityHandler.java b/servlet-core/src/main/java/tech/smartboot/servlet/handler/SecurityHandler.java index a217331..e3e8555 100644 --- a/servlet-core/src/main/java/tech/smartboot/servlet/handler/SecurityHandler.java +++ b/servlet-core/src/main/java/tech/smartboot/servlet/handler/SecurityHandler.java @@ -11,73 +11,70 @@ package tech.smartboot.servlet.handler; import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.ServletSecurity; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.smartboot.http.common.enums.HttpStatus; import tech.smartboot.servlet.conf.SecurityConstraint; import tech.smartboot.servlet.conf.UrlPattern; -import tech.smartboot.servlet.plugins.security.UserTO; +import tech.smartboot.servlet.plugins.security.LoginAccount; +import tech.smartboot.servlet.plugins.security.SecurityAccount; +import tech.smartboot.servlet.util.CollectionUtils; import tech.smartboot.servlet.util.PathMatcherUtil; import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; public class SecurityHandler extends Handler { @Override public void handleRequest(HandlerContext handlerContext) throws ServletException, IOException { HttpServletRequest request = (HttpServletRequest) handlerContext.getRequest(); -// UserTO userTO = null; -// if (!handlerContext.getServletInfo().getSecurityRoles().isEmpty()) { -// userTO = handlerContext.getServletContext().getRuntime().getSecurityProvider().getUser(request); -// if (userTO == null) { -// ((HttpServletResponse) handlerContext.getResponse()).sendError(403); -// return; -// } -// boolean valid = false; -// for (String role : handlerContext.getServletInfo().getSecurityRoles().values()) { -// if (userTO.getRoles().contains(role)) { -// valid = true; -// break; -// } -// } -// if (!valid) { -// ((HttpServletResponse) handlerContext.getResponse()).sendError(403); -// return; -// } -// } + List constraints = new ArrayList<>(handlerContext.getServletInfo().getSecurityConstraints()); - List constraints = handlerContext.getServletInfo().getSecurityConstraints(); - if (constraints.isEmpty()) { - constraints = handlerContext.getServletContext().getDeploymentInfo().getSecurityConstraints().stream().filter(securityConstraint -> { - for (UrlPattern urlPattern : securityConstraint.getUrlPatterns()) { - if (PathMatcherUtil.matches((HttpServletRequest) handlerContext.getRequest(), urlPattern)) { - return true; - } + constraints.addAll(handlerContext.getServletContext().getDeploymentInfo().getSecurityConstraints().stream().filter(securityConstraint -> { + for (UrlPattern urlPattern : securityConstraint.getUrlPatterns()) { + if (PathMatcherUtil.matches((HttpServletRequest) handlerContext.getRequest(), urlPattern)) { + return true; } - return false; - }).toList(); - } + } + return false; + }).toList()); + //不存在匹配的安全约束 if (constraints.isEmpty()) { doNext(handlerContext); return; } - constraints = constraints.stream().filter(securityConstraint -> securityConstraint.getRoleNames() == null || securityConstraint.getHttpMethods().isEmpty() || securityConstraint.getHttpMethods().contains(request.getMethod())).toList(); + + constraints = constraints.stream().filter(securityConstraint -> ((CollectionUtils.isNotEmpty(securityConstraint.getRoleNames()) || securityConstraint.getEmptyRoleSemantic() == ServletSecurity.EmptyRoleSemantic.PERMIT)) && ((securityConstraint.getHttpMethods().isEmpty() || securityConstraint.getHttpMethods().contains(request.getMethod())) && !securityConstraint.getHttpMethodOmissions().contains(request.getMethod()))).toList(); if (constraints.isEmpty()) { ((HttpServletResponse) handlerContext.getResponse()).sendError(403); return; } - UserTO userTO = handlerContext.getServletContext().getRuntime().getSecurityProvider().getUser(request); + SecurityAccount securityAccount = handlerContext.getServletContext().getRuntime().getSecurityProvider().login(request); + if (securityAccount == null) { + ((HttpServletResponse) handlerContext.getResponse()).sendError(HttpStatus.UNAUTHORIZED.value()); + return; + } + LoginAccount loginAccount = new LoginAccount(securityAccount.getUsername(), securityAccount.getPassword(), new HashSet<>()); constraints = constraints.stream().filter(securityConstraint -> { + if (securityConstraint.getEmptyRoleSemantic() == ServletSecurity.EmptyRoleSemantic.PERMIT && CollectionUtils.isEmpty(securityConstraint.getRoleNames())) { + return true; + } for (String role : securityConstraint.getRoleNames()) { - if (userTO.getRoles().contains(role)) { + if (securityAccount.getRoles().contains(role)) { + loginAccount.getRoles().add(role); return true; } } return false; }).toList(); if (constraints.isEmpty()) { - ((HttpServletResponse) handlerContext.getResponse()).sendError(403); + ((HttpServletResponse) handlerContext.getResponse()).sendError(HttpStatus.FORBIDDEN.value()); return; } + handlerContext.getOriginalRequest().setLoginAccount(loginAccount); doNext(handlerContext); } } diff --git a/servlet-core/src/main/java/tech/smartboot/servlet/impl/HttpServletRequestImpl.java b/servlet-core/src/main/java/tech/smartboot/servlet/impl/HttpServletRequestImpl.java index 8be2289..8be2e9d 100644 --- a/servlet-core/src/main/java/tech/smartboot/servlet/impl/HttpServletRequestImpl.java +++ b/servlet-core/src/main/java/tech/smartboot/servlet/impl/HttpServletRequestImpl.java @@ -41,6 +41,8 @@ import tech.smartboot.servlet.conf.ServletInfo; import tech.smartboot.servlet.conf.ServletMappingInfo; import tech.smartboot.servlet.impl.fileupload.SmartHttpRequestContext; +import tech.smartboot.servlet.plugins.security.LoginAccount; +import tech.smartboot.servlet.plugins.security.SecurityAccount; import tech.smartboot.servlet.provider.SessionProvider; import tech.smartboot.servlet.third.commons.fileupload.FileItem; import tech.smartboot.servlet.third.commons.fileupload.FileUpload; @@ -116,6 +118,7 @@ public class HttpServletRequestImpl implements SmartHttpServletRequest { private final CompletableFuture completableFuture; private ServletMappingInfo servletMappingInfo; + private LoginAccount principal; public HttpServletRequestImpl(HttpRequest request, ServletContextRuntime runtime, CompletableFuture completableFuture) { this.request = request; @@ -251,12 +254,19 @@ public String getRemoteUser() { @Override public boolean isUserInRole(String role) { - throw new UnsupportedOperationException(); + return runtime.getSecurityProvider().isUserInRole(role, principal, this); } @Override public Principal getUserPrincipal() { - return null; + if (principal == null) { +// try { +// principal = runtime.getSecurityProvider().getUser(this); +// } catch (ServletException e) { +// throw new RuntimeException(e); +// } + } + return principal; } @Override @@ -378,7 +388,10 @@ public boolean authenticate(HttpServletResponse response) { @Override public void login(String username, String password) throws ServletException { - throw new ServletException("Not Implemented"); + SecurityAccount securityAccount = runtime.getSecurityProvider().login(username, password); + if (securityAccount != null) { + principal = new LoginAccount(securityAccount.getUsername(), securityAccount.getPassword(), securityAccount.getRoles()); + } } @Override @@ -826,6 +839,11 @@ public void setAsyncSupported(boolean supported) { this.asyncSupported = asyncStarted; } + @Override + public void setLoginAccount(LoginAccount loginAccount) { + this.principal = loginAccount; + } + @Override public AsyncContext getAsyncContext() { if (isAsyncStarted()) { diff --git a/servlet-core/src/main/java/tech/smartboot/servlet/plugins/dispatcher/ServletRequestDispatcherWrapper.java b/servlet-core/src/main/java/tech/smartboot/servlet/plugins/dispatcher/ServletRequestDispatcherWrapper.java index 152667d..e432134 100644 --- a/servlet-core/src/main/java/tech/smartboot/servlet/plugins/dispatcher/ServletRequestDispatcherWrapper.java +++ b/servlet-core/src/main/java/tech/smartboot/servlet/plugins/dispatcher/ServletRequestDispatcherWrapper.java @@ -22,6 +22,7 @@ import tech.smartboot.servlet.conf.ServletMappingInfo; import tech.smartboot.servlet.impl.HttpServletMappingImpl; import tech.smartboot.servlet.impl.HttpServletRequestImpl; +import tech.smartboot.servlet.plugins.security.LoginAccount; import java.util.Collections; import java.util.Enumeration; @@ -167,6 +168,11 @@ public void setAsyncSupported(boolean supported) { this.request.setAsyncSupported(supported); } + @Override + public void setLoginAccount(LoginAccount loginAccount) { + this.request.setLoginAccount(loginAccount); + } + @Override public HttpServletMapping getHttpServletMapping() { diff --git a/servlet-core/src/main/java/tech/smartboot/servlet/plugins/security/UserTO.java b/servlet-core/src/main/java/tech/smartboot/servlet/plugins/security/LoginAccount.java similarity index 60% rename from servlet-core/src/main/java/tech/smartboot/servlet/plugins/security/UserTO.java rename to servlet-core/src/main/java/tech/smartboot/servlet/plugins/security/LoginAccount.java index 3666af8..227e6af 100644 --- a/servlet-core/src/main/java/tech/smartboot/servlet/plugins/security/UserTO.java +++ b/servlet-core/src/main/java/tech/smartboot/servlet/plugins/security/LoginAccount.java @@ -10,40 +10,31 @@ package tech.smartboot.servlet.plugins.security; +import java.security.Principal; import java.util.Set; -public class UserTO { - private String username; - private String password; - private Set roles; +public class LoginAccount implements Principal { + private final String username; + private final String password; + private final Set roles; - public UserTO(String username, String password, Set roles) { + public LoginAccount(String username, String password, Set roles) { this.username = username; this.password = password; this.roles = roles; } - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } public String getPassword() { return password; } - public void setPassword(String password) { - this.password = password; - } - public Set getRoles() { return roles; } - public void setRoles(Set roles) { - this.roles = roles; + @Override + public String getName() { + return username; } } diff --git a/servlet-core/src/main/java/tech/smartboot/servlet/plugins/security/SecurityAccount.java b/servlet-core/src/main/java/tech/smartboot/servlet/plugins/security/SecurityAccount.java new file mode 100644 index 0000000..2f70956 --- /dev/null +++ b/servlet-core/src/main/java/tech/smartboot/servlet/plugins/security/SecurityAccount.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) [2022] smartboot [zhengjunweimail@163.com] + * + * 企业用户未经smartboot组织特别许可,需遵循AGPL-3.0开源协议合理合法使用本项目。 + * + * Enterprise users are required to use this project reasonably + * and legally in accordance with the AGPL-3.0 open source agreement + * without special permission from the smartboot organization. + */ + +package tech.smartboot.servlet.plugins.security; + +import java.util.Collections; +import java.util.Set; + +/** + * 当前容器配置的认证信息 + * + * @author 三刀 + */ +public class SecurityAccount { + public static final String AUTH_TYPE_BASIC = "BASIC"; + public static final String FORM = "FORM"; + public static final String DIGEST = "DIGEST"; + public static final String CLIENT_CERT = "CLIENT_CERT"; + public static final String NONE = "NONE"; + private final String username; + private final String password; + private final String authType; + private final Set roles; + + public SecurityAccount(String username, String password, String authType, Set roles) { + this.username = username; + this.password = password; + this.authType = authType; + this.roles = Collections.unmodifiableSet(roles); + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public Set getRoles() { + return roles; + } +} diff --git a/servlet-core/src/main/java/tech/smartboot/servlet/plugins/security/SecurityPlugin.java b/servlet-core/src/main/java/tech/smartboot/servlet/plugins/security/SecurityPlugin.java new file mode 100644 index 0000000..7ce518f --- /dev/null +++ b/servlet-core/src/main/java/tech/smartboot/servlet/plugins/security/SecurityPlugin.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) [2022] smartboot [zhengjunweimail@163.com] + * + * 企业用户未经smartboot组织特别许可,需遵循AGPL-3.0开源协议合理合法使用本项目。 + * + * Enterprise users are required to use this project reasonably + * and legally in accordance with the AGPL-3.0 open source agreement + * without special permission from the smartboot organization. + */ + +package tech.smartboot.servlet.plugins.security; + +import tech.smartboot.servlet.Container; +import tech.smartboot.servlet.plugins.Plugin; + +public class SecurityPlugin extends Plugin { + @Override + protected void initPlugin(Container container) { + super.initPlugin(container); + } +} diff --git a/servlet-core/src/main/java/tech/smartboot/servlet/plugins/security/SecurityProviderImpl.java b/servlet-core/src/main/java/tech/smartboot/servlet/plugins/security/SecurityProviderImpl.java index e8fae57..ea9aea8 100644 --- a/servlet-core/src/main/java/tech/smartboot/servlet/plugins/security/SecurityProviderImpl.java +++ b/servlet-core/src/main/java/tech/smartboot/servlet/plugins/security/SecurityProviderImpl.java @@ -19,6 +19,7 @@ import tech.smartboot.servlet.provider.SecurityProvider; import java.io.IOException; +import java.util.Arrays; import java.util.Base64; import java.util.HashMap; import java.util.List; @@ -30,11 +31,12 @@ public class SecurityProviderImpl implements SecurityProvider { private Map prefixPathSecurities = new HashMap<>(); private Map extensionSecurities = new HashMap<>(); private Map methodSecurities = new HashMap<>(); - private final Map headerSecurities = new HashMap<>(); + private final Map headerSecurities = new HashMap<>(); + private List users = Arrays.asList(new SecurityAccount("j2ee", "j2ee", null, Set.of("Administrator", "Employee")), new SecurityAccount("javajoe", "javajoe", null, Set.of("VP", "Manager"))); @Override public void addUser(String username, String password, Set roles) { - headerSecurities.put(username, new UserTO(username, password, roles)); + headerSecurities.put(username, new SecurityAccount(username, password, null, roles)); } @Override @@ -43,8 +45,8 @@ public void init(List constraints) { } @Override - public void login(String username, String password, HttpServletRequestImpl httpServletRequest) throws ServletException { - + public SecurityAccount login(String username, String password) throws ServletException { + return users.stream().filter(user -> user.getUsername().equals(username) && user.getPassword().equals(password)).findFirst().orElse(null); } @Override @@ -53,7 +55,20 @@ public boolean authenticate(HttpServletRequestImpl httpServletRequest, HttpServl } @Override - public boolean isUserInRole(String role, HttpServletRequestImpl httpServletRequest) { + public boolean isUserInRole(String role, LoginAccount loginAccount, HttpServletRequestImpl httpServletRequest) { + if (loginAccount == null) { + return false; + } + if (role == null || role.equals("*")) { + return false; + } + if (role.equals("**")) { + Set roles = httpServletRequest.getServletContext().getDeploymentInfo().getSecurityRoles(); + if (!roles.contains("**")) { + return true; + } + } + return false; } @@ -63,16 +78,15 @@ public void logout(HttpServletRequestImpl httpServletRequest) throws ServletExce } @Override - public UserTO getUser(HttpServletRequest request) { + public SecurityAccount login(HttpServletRequest request) throws ServletException { String authorization = request.getHeader("Authorization"); if (StringUtils.isBlank(authorization)) { return null; } - UserTO userTO = new UserTO("j2ee", "j2ee", Set.of("Administrator", "Employee")); if (authorization.startsWith("Basic ")) { - System.out.println(new String(Base64.getDecoder().decode(authorization.substring(6)))); - + String[] auth = new String(Base64.getDecoder().decode(authorization.substring(6))).split(":"); + return users.stream().filter(user -> user.getUsername().equals(auth[0]) && user.getPassword().equals(auth[1])).findFirst().orElse(null); } - return userTO; + return null; } } diff --git a/servlet-core/src/main/java/tech/smartboot/servlet/provider/SecurityProvider.java b/servlet-core/src/main/java/tech/smartboot/servlet/provider/SecurityProvider.java index 21f190a..2da0863 100644 --- a/servlet-core/src/main/java/tech/smartboot/servlet/provider/SecurityProvider.java +++ b/servlet-core/src/main/java/tech/smartboot/servlet/provider/SecurityProvider.java @@ -15,7 +15,8 @@ import jakarta.servlet.http.HttpServletResponse; import tech.smartboot.servlet.conf.SecurityConstraint; import tech.smartboot.servlet.impl.HttpServletRequestImpl; -import tech.smartboot.servlet.plugins.security.UserTO; +import tech.smartboot.servlet.plugins.security.LoginAccount; +import tech.smartboot.servlet.plugins.security.SecurityAccount; import java.io.IOException; import java.util.List; @@ -26,14 +27,14 @@ public interface SecurityProvider { void init(List constraints); - public void login(String username, String password, HttpServletRequestImpl httpServletRequest) throws ServletException; + public SecurityAccount login(String username, String password) throws ServletException; public boolean authenticate(HttpServletRequestImpl httpServletRequest, HttpServletResponse response) throws IOException, ServletException; - public boolean isUserInRole(String role, HttpServletRequestImpl httpServletRequest); + public boolean isUserInRole(String role, LoginAccount loginAccount, HttpServletRequestImpl httpServletRequest); public void logout(HttpServletRequestImpl httpServletRequest) throws ServletException; - public UserTO getUser(HttpServletRequest request); + public SecurityAccount login(HttpServletRequest request) throws ServletException; } diff --git a/servlet-core/src/main/resources/META-INF/services/tech.smartboot.servlet.plugins.Plugin b/servlet-core/src/main/resources/META-INF/services/tech.smartboot.servlet.plugins.Plugin index ac6a49b..d3f05cc 100644 --- a/servlet-core/src/main/resources/META-INF/services/tech.smartboot.servlet.plugins.Plugin +++ b/servlet-core/src/main/resources/META-INF/services/tech.smartboot.servlet.plugins.Plugin @@ -15,4 +15,5 @@ tech.smartboot.servlet.plugins.dispatcher.DispatcherPlugin tech.smartboot.servlet.plugins.contact.ContactPlugin tech.smartboot.servlet.plugins.websocket.WebsocketPlugin tech.smartboot.servlet.plugins.async.AsyncContextPlugin -tech.smartboot.servlet.plugins.mapping.MappingPlugin \ No newline at end of file +tech.smartboot.servlet.plugins.mapping.MappingPlugin +tech.smartboot.servlet.plugins.security.SecurityPlugin \ No newline at end of file