From 46a1b2094d22e11f9298d7253397b5b15ed98867 Mon Sep 17 00:00:00 2001 From: zhuyijun Date: Sun, 16 Oct 2022 15:55:49 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8E=88=E6=9D=83=E7=A0=81?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AuthorizationServerConfiguration.java | 1 - .../config/security/ResourceServerConfig.java | 4 +- .../server/client/po/OauthClientDetail.java | 45 ++++--- .../user/handler/OauthRequestValidator.java | 119 ++++++++++++++++++ .../user/service/impl/AuthServiceImpl.java | 55 ++++---- .../src/main/resources/banner.txt | 15 +++ .../src/main/resources/bootstrap.yml | 1 + .../oauth/resource/ResourceServerConfig.java | 4 + 8 files changed, 189 insertions(+), 55 deletions(-) create mode 100644 server/zyjblogs-oauth/src/main/java/cn/zyjblogs/server/user/handler/OauthRequestValidator.java create mode 100644 server/zyjblogs-oauth/src/main/resources/banner.txt diff --git a/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/config/security/AuthorizationServerConfiguration.java b/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/config/security/AuthorizationServerConfiguration.java index 81e2212..2a1a7f5 100644 --- a/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/config/security/AuthorizationServerConfiguration.java +++ b/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/config/security/AuthorizationServerConfiguration.java @@ -40,7 +40,6 @@ public class AuthorizationServerConfiguration extends AuthorizationServerConfigu private final DataSource dataSource; private final JwtTokenEnhancer jwtTokenEnhancer; private final OauthResponseExceptionTranslator oAuthResponseExceptionTranslator; - private final ClientDetailsService oauthClientDetailsService; private final RedisTemplateHandler redisTemplateHandler; /** diff --git a/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/config/security/ResourceServerConfig.java b/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/config/security/ResourceServerConfig.java index 679a084..f22e4dd 100644 --- a/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/config/security/ResourceServerConfig.java +++ b/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/config/security/ResourceServerConfig.java @@ -52,7 +52,7 @@ public class ResourceServerConfig extends ResourceServerConfigurerAdapter { .disable() //限制资源服务器作用范围为 "/user/**", "/demo/**" .requestMatchers().antMatchers("/v*/**", "/demo/**", - String.join(",", whiteListProperties.getAllowPaths())) + String.join(",", whiteListProperties.getAllowPaths())) .and() .formLogin() .and() @@ -60,9 +60,11 @@ public class ResourceServerConfig extends ResourceServerConfigurerAdapter { .antMatchers(String.join(",", whiteListProperties.getAllowPaths())) .permitAll() .antMatchers("/v*/user/login", "/v*/auth/refresh/token", "/v*/auth/authorize/code").permitAll() + .antMatchers("/v*/**").access("#oauth2.hasAnyScope('oauth','all')") //以下请求必须认证通过 .anyRequest() .authenticated().and() + .httpBasic(); } diff --git a/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/server/client/po/OauthClientDetail.java b/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/server/client/po/OauthClientDetail.java index a5ee7b4..4784e53 100644 --- a/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/server/client/po/OauthClientDetail.java +++ b/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/server/client/po/OauthClientDetail.java @@ -10,12 +10,6 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; import org.codehaus.jackson.annotate.JsonIgnore; import org.codehaus.jackson.annotate.JsonIgnoreProperties; import org.codehaus.jackson.annotate.JsonProperty; @@ -25,12 +19,7 @@ import org.springframework.util.StringUtils; import java.io.Serializable; import java.time.LocalDateTime; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * @author zhuyijun @@ -46,7 +35,7 @@ import java.util.Set; ignoreUnknown = true ) @TableName("oauth_client_details") -public class OauthClientDetail implements ClientDetails , Serializable { +public class OauthClientDetail implements ClientDetails, Serializable { @TableId("client_id") private String clientId; @@ -121,18 +110,18 @@ public class OauthClientDetail implements ClientDetails , Serializable { public OauthClientDetail() { } - public OauthClientDetail(ClientDetails prototype){ + public OauthClientDetail(ClientDetails prototype) { this(); this.setAccessTokenValiditySeconds(prototype.getAccessTokenValiditySeconds()); this.setRefreshTokenValiditySeconds(prototype.getRefreshTokenValiditySeconds()); Collection authorities = prototype.getAuthorities(); this.setAuthorities(JSON.toJSONString(authorities)); - this.setAuthorizedGrantTypes(String.join(",",prototype.getAuthorizedGrantTypes())); + this.setAuthorizedGrantTypes(String.join(",", prototype.getAuthorizedGrantTypes())); this.setClientId(prototype.getClientId()); this.setClientSecret(prototype.getClientSecret()); - this.setWebServerRedirectUri(String.join(",",prototype.getRegisteredRedirectUri())); - this.setScope(String.join(",",prototype.getScope())); - this.setResourceIds(String.join(",",prototype.getResourceIds())); + this.setWebServerRedirectUri(String.join(",", prototype.getRegisteredRedirectUri())); + this.setScope(String.join(",", prototype.getScope())); + this.setResourceIds(String.join(",", prototype.getResourceIds())); } @Override @@ -215,7 +204,7 @@ public class OauthClientDetail implements ClientDetails , Serializable { @JsonIgnore @com.fasterxml.jackson.annotation.JsonIgnore public Set getAuthorizedGrantTypes() { - if (authorizedGrantTypes == null){ + if (authorizedGrantTypes == null) { return new HashSet<>(0); } return Set.of(authorizedGrantTypes.split(",")); @@ -225,7 +214,7 @@ public class OauthClientDetail implements ClientDetails , Serializable { @JsonIgnore @com.fasterxml.jackson.annotation.JsonIgnore public Set getRegisteredRedirectUri() { - if (webServerRedirectUri == null){ + if (webServerRedirectUri == null) { return new HashSet<>(0); } return Set.of(webServerRedirectUri.split(",")); @@ -236,7 +225,7 @@ public class OauthClientDetail implements ClientDetails , Serializable { @JsonIgnore @com.fasterxml.jackson.annotation.JsonIgnore public Collection getAuthorities() { - if (StringUtils.isEmpty(authorities)){ + if (StringUtils.isEmpty(authorities)) { return Collections.emptyList(); } return JSON.parseArray(authorities, GrantedAuthority.class); @@ -259,15 +248,23 @@ public class OauthClientDetail implements ClientDetails , Serializable { @Override @JsonIgnore @com.fasterxml.jackson.annotation.JsonIgnore - public boolean isAutoApprove(String s) { - return Boolean.getBoolean(autoapprove); + public boolean isAutoApprove(String scope) { + if (scope == null) { + return false; + } + for (String auto : getScope()) { + if (auto.equals("true") || scope.matches(auto)) { + return true; + } + } + return false; } @Override @JsonIgnore @com.fasterxml.jackson.annotation.JsonIgnore public Map getAdditionalInformation() { - if (additionalInformation == null){ + if (additionalInformation == null) { return new HashMap<>(0); } return BeanUtils.beanToMap(additionalInformation); diff --git a/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/server/user/handler/OauthRequestValidator.java b/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/server/user/handler/OauthRequestValidator.java new file mode 100644 index 0000000..0994084 --- /dev/null +++ b/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/server/user/handler/OauthRequestValidator.java @@ -0,0 +1,119 @@ +package cn.zyjblogs.server.user.handler; + +import cn.zyjblogs.starter.common.entity.context.BaseContext; +import lombok.RequiredArgsConstructor; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientRegistrationException; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.approval.Approval; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.stereotype.Component; + +import java.util.*; + +@Component +@RequiredArgsConstructor +public class OauthRequestValidator { + + private final TokenStore tokenStore; + private final JwtAccessTokenConverter jwtAccessTokenConverter; + + public void validateScope(Set requestScopes, Set clientScopes) { + + if (clientScopes != null && !clientScopes.isEmpty()) { + for (String scope : requestScopes) { + if (!clientScopes.contains(scope)) { + throw new InvalidScopeException("Invalid scope: " + scope, clientScopes); + } + } + } + + if (requestScopes.isEmpty()) { + throw new InvalidScopeException("Empty scope (either the client or the user is not allowed the requested scopes)"); + } + } + + public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, ClientDetails client) { + + String clientId = authorizationRequest.getClientId(); + Collection requestedScopes = authorizationRequest.getScope(); + Set approvedScopes = new HashSet(); + Set validUserApprovedScopes = new HashSet(); + Set approvals = new HashSet(); + if (client != null) { + try { + for (String scope : requestedScopes) { + if (client.isAutoApprove(scope)) { + approvedScopes.add(scope); + } + } + if (approvedScopes.containsAll(requestedScopes)) { + // gh-877 - if all scopes are auto approved, approvals still need to be added to the approval store. + Date expiry = computeExpiry(-1); + for (String approvedScope : approvedScopes) { + approvals.add(new Approval(BaseContext.getUsername(), authorizationRequest.getClientId(), + approvedScope, expiry, Approval.ApprovalStatus.APPROVED)); + } + + authorizationRequest.setApproved(true); + return authorizationRequest; + } + } catch (ClientRegistrationException e) { + } + } + + + // Find the stored approvals for that user and client + Collection userApprovals = getApprovals(clientId); + // Look at the scopes and see if they have expired + Date today = new Date(); + for (Approval approval : userApprovals) { + if (approval.getExpiresAt().after(today)) { + if (approval.getStatus() == Approval.ApprovalStatus.APPROVED) { + validUserApprovedScopes.add(approval.getScope()); + approvedScopes.add(approval.getScope()); + } + } + } + + // If the requested scopes have already been acted upon by the user, + // this request is approved + if (validUserApprovedScopes.containsAll(requestedScopes)) { + approvedScopes.retainAll(requestedScopes); + // Set only the scopes that have been approved by the user + authorizationRequest.setScope(approvedScopes); + authorizationRequest.setApproved(true); + } + + return authorizationRequest; + + } + + private Date computeExpiry(int approvalExpirySeconds) { + Calendar expiresAt = Calendar.getInstance(); + if (approvalExpirySeconds == -1) { // use default of 1 month + expiresAt.add(Calendar.MONTH, 1); + } else { + expiresAt.add(Calendar.SECOND, approvalExpirySeconds); + } + return expiresAt.getTime(); + } + + private Collection getApprovals(String clientId) { + Collection result = new HashSet(); + OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(BaseContext.getToken()); + OAuth2Authentication authentication = tokenStore.readAuthentication(BaseContext.getToken()); + if (authentication != null) { + Date expiresAt = oAuth2AccessToken.getExpiration(); + for (String scope : oAuth2AccessToken.getScope()) { + result.add(new Approval(BaseContext.getUsername(), clientId, scope, expiresAt, Approval.ApprovalStatus.APPROVED)); + } + } + return result; + } + +} diff --git a/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/server/user/service/impl/AuthServiceImpl.java b/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/server/user/service/impl/AuthServiceImpl.java index f70c1f1..805bc3d 100644 --- a/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/server/user/service/impl/AuthServiceImpl.java +++ b/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/server/user/service/impl/AuthServiceImpl.java @@ -4,6 +4,7 @@ import cn.zyjblogs.server.user.dto.AuthCodeDto; import cn.zyjblogs.server.user.dto.AuthorizationCodeDto; import cn.zyjblogs.server.user.dto.AuthorizationDto; import cn.zyjblogs.server.user.dto.OAuth2AccessTokenDto; +import cn.zyjblogs.server.user.handler.OauthRequestValidator; import cn.zyjblogs.server.user.handler.OauthRquestHander; import cn.zyjblogs.server.user.po.OauthUserDetails; import cn.zyjblogs.server.user.service.AuthService; @@ -21,25 +22,17 @@ import org.springframework.security.oauth2.common.exceptions.UnsupportedResponse import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException; import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration; -import org.springframework.security.oauth2.provider.AuthorizationRequest; -import org.springframework.security.oauth2.provider.ClientDetails; -import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.OAuth2Request; -import org.springframework.security.oauth2.provider.OAuth2RequestFactory; -import org.springframework.security.oauth2.provider.TokenGranter; -import org.springframework.security.oauth2.provider.TokenRequest; +import org.springframework.security.oauth2.provider.*; import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; +import org.springframework.security.oauth2.provider.endpoint.DefaultRedirectResolver; +import org.springframework.security.oauth2.provider.endpoint.RedirectResolver; import org.springframework.security.oauth2.provider.implicit.ImplicitTokenRequest; import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * @author zhuyijun @@ -51,16 +44,20 @@ public class AuthServiceImpl implements AuthService { private final OAuth2RequestFactory oAuth2RequestFactory; private final AuthorizationCodeServices authorizationCodeServices; private final PasswordEncoder passwordEncoder; + private RedirectResolver redirectResolver; + private OauthRequestValidator oauthRequestValidator; private final Object implicitLock = new Object(); public AuthServiceImpl(AuthorizationServerEndpointsConfiguration authorizationServerEndpointsConfiguration, ClientDetailsService clientDetails, - AuthorizationCodeServices authorizationCodeServices, PasswordEncoder passwordEncoder) { + AuthorizationCodeServices authorizationCodeServices, PasswordEncoder passwordEncoder, OauthRequestValidator oauthRequestValidator) { this.tokenGranter = authorizationServerEndpointsConfiguration.getEndpointsConfigurer().getTokenGranter(); this.clientDetails = clientDetails; + this.redirectResolver = new DefaultRedirectResolver(); this.oAuth2RequestFactory = new DefaultOAuth2RequestFactory(clientDetails); this.authorizationCodeServices = authorizationCodeServices; this.passwordEncoder = passwordEncoder; + this.oauthRequestValidator = oauthRequestValidator; } @Value("${security.oauth2.client.client-id}") @@ -116,25 +113,25 @@ public class AuthServiceImpl implements AuthService { return OauthRquestHander.getUnsuccessfulRedirect(redirect_url, new OAuth2Exception("无此客户端"), false); } if (!passwordEncoder.matches(authorizationCodeDto.getClient_secret(), clientDetail.getClientSecret())) { - return OauthRquestHander.getUnsuccessfulRedirect(redirect_url, new OAuth2Exception("客户端" + authorizationCodeDto.getClient_id() + "认证失败"), false); + throw new AuthRuntimeException(HttpCode.BAD_REQUEST, "该客户端认证失败"); } + String resolvedRedirect = redirectResolver.resolveRedirect(authorizationCodeDto.getRedirect_url(), clientDetail); + String scope = authorizationCodeDto.getScope(); + Set scopes = StringUtils.hasLength(scope) ? Set.of(scope.split(",")) : new HashSet<>(); + oauthRequestValidator.validateScope(scopes, clientDetail.getScope()); Map parameters = new HashMap<>(16); parameters.put(OAuth2Utils.CLIENT_ID, authorizationCodeDto.getClient_id()); parameters.put("client_secret", authorizationCodeDto.getClient_secret()); - parameters.put(OAuth2Utils.REDIRECT_URI, redirect_url); + parameters.put(OAuth2Utils.REDIRECT_URI, resolvedRedirect); parameters.put(OAuth2Utils.RESPONSE_TYPE, "code"); parameters.put(OAuth2Utils.SCOPE, authorizationCodeDto.getScope()); parameters.put(OAuth2Utils.STATE, authorizationCodeDto.getState()); parameters.put(OAuth2Utils.USER_OAUTH_APPROVAL, authorizationCodeDto.getUser_oauth_approval()); AuthorizationRequest authorizationRequest = createAuthorizationRequest(parameters, clientDetail); - Set registeredRedirectUri = clientDetail.getRegisteredRedirectUri(); - if (!redirect_url.equals(registeredRedirectUri.toArray()[0].toString())) { - return OauthRquestHander.getUnsuccessfulRedirect(authorizationRequest, - new UserDeniedAuthorizationException("回调地址不一致"), false); - } + oauthRequestValidator.checkForPreApproval(authorizationRequest, clientDetail); String flag = parameters.get(OAuth2Utils.USER_OAUTH_APPROVAL); boolean isApproved = "true".equalsIgnoreCase(flag); - authorizationRequest.setApproved(isApproved); + authorizationRequest.setApproved(authorizationRequest.isApproved() && isApproved); if (!isApproved) { return OauthRquestHander.getUnsuccessfulRedirect(authorizationRequest, new UserDeniedAuthorizationException("User denied access"), false); @@ -142,15 +139,15 @@ public class AuthServiceImpl implements AuthService { if (responseTypes.contains("token")) { return getImplicitGrantResponse(authorizationRequest); } + AuthorizationDto authorizationDto = new AuthorizationDto(); + OauthUserDetails oauthUserDetails = new OauthUserDetails(); + oauthUserDetails.setId(BaseContext.getUserId()); + oauthUserDetails.setName(BaseContext.getName()); + oauthUserDetails.setUsername(BaseContext.getUsername()); + oauthUserDetails.setTenantId(BaseContext.getTenantId()); + authorizationDto.setPrincipal(oauthUserDetails); + authorizationDto.setAuthenticated(true); try { - AuthorizationDto authorizationDto = new AuthorizationDto(); - OauthUserDetails oauthUserDetails = new OauthUserDetails(); - oauthUserDetails.setId(BaseContext.getUserId()); - oauthUserDetails.setName(BaseContext.getName()); - oauthUserDetails.setUsername(BaseContext.getUsername()); - oauthUserDetails.setTenantId(BaseContext.getTenantId()); - authorizationDto.setPrincipal(oauthUserDetails); - authorizationDto.setAuthenticated(true); return OauthRquestHander.getSuccessfulRedirect(authorizationRequest, generateCode(authorizationRequest, authorizationDto)); } catch (OAuth2Exception e) { diff --git a/server/zyjblogs-oauth/src/main/resources/banner.txt b/server/zyjblogs-oauth/src/main/resources/banner.txt new file mode 100644 index 0000000..e52a19b --- /dev/null +++ b/server/zyjblogs-oauth/src/main/resources/banner.txt @@ -0,0 +1,15 @@ + + // + \\ // + \\ // + ##DDDDDDDDDDDDDDDDDDDDDD## + ## DDDDDDDDDDDDDDDDDDDD ## ________ ___ ___ ___ ________ ___ ___ ___ + ## hh hh ## |\ __ \ |\ \ |\ \ |\ \ |\ __ \ |\ \ |\ \ |\ \ + ## hh // \\ hh ## \ \ \|\ /_\ \ \\ \ \ \ \ \\ \ \|\ /_\ \ \\ \ \ \ \ \ + ## hh // \\ hh ## \ \ __ \\ \ \\ \ \ \ \ \\ \ __ \\ \ \\ \ \ \ \ \ + ## hh hh ## \ \ \|\ \\ \ \\ \ \____ \ \ \\ \ \|\ \\ \ \\ \ \____ \ \ \ + ## hh wwww hh ## \ \_______\\ \__\\ \_______\\ \__\\ \_______\\ \__\\ \_______\\ \__\ + ## hh hh ## \|_______| \|__| \|_______| \|__| \|_______| \|__| \|_______| \|__| + ## MMMMMMMMMMMMMMMMMMMM ## + ##MMMMMMMMMMMMMMMMMMMMMM## Release 1.6.11. Powered by jinkela-core 2.8.9. + \/ \/ \ No newline at end of file diff --git a/server/zyjblogs-rbac/src/main/resources/bootstrap.yml b/server/zyjblogs-rbac/src/main/resources/bootstrap.yml index a6ea310..9bcb96a 100644 --- a/server/zyjblogs-rbac/src/main/resources/bootstrap.yml +++ b/server/zyjblogs-rbac/src/main/resources/bootstrap.yml @@ -104,6 +104,7 @@ security: client: client-id: ${spring.application.name} client-secret: secret + scope: user resource: id: ${spring.application.name} diff --git a/stater/zyjblogs-oauth-spring-boot-starter/src/main/java/cn/zyjblogs/starter/oauth/resource/ResourceServerConfig.java b/stater/zyjblogs-oauth-spring-boot-starter/src/main/java/cn/zyjblogs/starter/oauth/resource/ResourceServerConfig.java index b1c0d7f..e47b61d 100644 --- a/stater/zyjblogs-oauth-spring-boot-starter/src/main/java/cn/zyjblogs/starter/oauth/resource/ResourceServerConfig.java +++ b/stater/zyjblogs-oauth-spring-boot-starter/src/main/java/cn/zyjblogs/starter/oauth/resource/ResourceServerConfig.java @@ -26,6 +26,8 @@ import org.springframework.security.oauth2.provider.token.TokenStore; public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Value("${security.oauth2.resource.id}") private String resourceId; + @Value("${security.oauth2.client.scope}") + private String scope; private final TokenStore tokenStore; private final WhiteListProperties whiteListProperties; @@ -42,9 +44,11 @@ public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { + String scopeRs = "#oauth2.hasAnyScope(" + '\'' + scope + '\'' + ",'all')"; http.csrf().disable() .authorizeRequests() .antMatchers("/webjars/**", "/swagger-ui.html/**", "/swagger-resources/**", "/v2/api-docs/**", String.join(",", whiteListProperties.getAllowPaths())).permitAll() + .antMatchers("/**").access(scopeRs) .anyRequest() .authenticated() .and()