我尝试设置一个简单的应用程序,该应用程序基于包含JWT的授权基本标头来授权REST服务中的操作。我已经使用了自动提供的oauth / token端点,以便客户端可以代表有效用户获得JWT。不幸的是,Spring Security阻止了所有请求,所以我在配置上做错了,我希望有人能看到它是什么。
这是我的配置:
数据源(用户和客户端设置的位置为:
@Configuration
public class DatasourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource datasource(Environment env){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(env.getRequiredProperty("url"));
dataSource.setUsername(env.getRequiredProperty("username"));
dataSource.setPassword(env.getRequiredProperty("password"));
return dataSource;
}
}
安全配置:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${security.signing-key}")
private String signingKey;
@Value("${security.security-realm}")
private String securityRealm;
@Autowired
@Qualifier("appUserDetailsService")
private UserDetailsService userDetailsService;
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private DataSource datasource;
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder passwordEncoder =
PasswordEncoderFactories.createDelegatingPasswordEncoder();
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic()
.realmName(securityRealm)
.and()
.csrf()
.disable();
}
@Bean
public JwtAccessTokenConverter jwtTokenEnhancer() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(signingKey);
return converter;
}
@Bean
public JdbcTokenStore tokenStore() {
return new JdbcTokenStore(datasource);
}
@Bean
@Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){
TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore);
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
handler.setClientDetailsService(clientDetailsService);
return handler;
}
@Bean
@Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
验证服务器:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private static String REALM="AVMAINT_REALM";
@Autowired
@Qualifier("appUserDetailsService")
private UserDetailsService userDetailsService;
@Autowired
private UserApprovalHandler userApprovalHandler;
@Autowired
private DataSource datasource;
@Autowired
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter jwtTokenEnhancer;
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(datasource);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore).tokenEnhancer(jwtTokenEnhancer).userApprovalHandler(userApprovalHandler)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.realm(REALM);
}
}
并将我的路径与授权分开,我有资源服务器
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Value("${security.jwt.resource-ids}")
private String resourceIds;
@Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.and()
.authorizeRequests()
.antMatchers("/actuator/**", "/api-docs/**", "/oauth/token", "/auth/login").permitAll()
.antMatchers("/api/**" ).authenticated()
.and()
.formLogin()
.successHandler(successHandler())
.failureHandler(failureHandler())
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.authenticationEntryPoint(authenticationEntryPoint())
;
}
private AuthenticationSuccessHandler successHandler() {
return new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.getWriter().append("OK");
httpServletResponse.setStatus(200);
}
};
}
private AuthenticationFailureHandler failureHandler() {
return new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.getWriter().append("Authentication failure");
httpServletResponse.setStatus(401);
}
};
}
private AccessDeniedHandler accessDeniedHandler() {
return new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.getWriter().append("Access denied");
httpServletResponse.setStatus(403);
}
};
}
private AuthenticationEntryPoint authenticationEntryPoint() {
return new AuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.getWriter().append("Not authenticated");
httpServletResponse.setStatus(401);
}
};
}
}
这是数据设置:
CREATE TABLE user (
id bigint NOT NULL AUTO_INCREMENT,
username varchar(64) NOT NULL,
password varchar(64) NOT NULL,
organisation_id bigint,
person_id bigint,
PRIMARY KEY (id),
CONSTRAINT fk_user_organisation FOREIGN KEY (organisation_id) REFERENCES organisation(id),
CONSTRAINT fk_user_person FOREIGN KEY (person_id) REFERENCES person(id)
);
CREATE TABLE role (
id bigint NOT NULL AUTO_INCREMENT,
description varchar(255) DEFAULT NULL,
role_name varchar(255) DEFAULT NULL,
PRIMARY KEY (id)
);
CREATE TABLE user_role (
user_id bigint,
role_id bigint,
CONSTRAINT fk_user_role_user FOREIGN KEY (user_id) REFERENCES user(id),
CONSTRAINT fk_user_role_role FOREIGN KEY (role_id) REFERENCES role(id)
);
INSERT INTO role (id, role_name, description) VALUES (1, 'Root User - Has permission to perform admin tasks', 'ROOT_USER');
INSERT INTO role (id, role_name, description) VALUES (2, 'Admin User - Has permission to admin organisation', 'ADMIN_USER');
INSERT INTO role (id, role_name, description) VALUES (3, 'Standard User - Has no admin rights', 'STANDARD_USER');
-- USER
-- non-encrypted password: jwtpass
INSERT INTO user (id, password, username) VALUES (1, '$2y$12$AyfKqP6YvubgFVHp0AGzs.VmrDaoIja3rUWncFkpLBSERGqAY94Vm', 'xxxxxxxx@xxxxxxx.com.au');
INSERT INTO user (id, password, username) VALUES (2, '$2y$12$AyfKqP6YvubgFVHp0AGzs.VmrDaoIja3rUWncFkpLBSERGqAY94Vm', 'frank@frankthring.org');
INSERT INTO user (id, password, username) VALUES (3, '$2y$12$AyfKqP6YvubgFVHp0AGzs.VmrDaoIja3rUWncFkpLBSERGqAY94Vm', 'mike@frankthring.org');
INSERT INTO user (id, password, username) VALUES (4, '$2y$12$AyfKqP6YvubgFVHp0AGzs.VmrDaoIja3rUWncFkpLBSERGqAY94Vm', 'xxxxxxx@xxxxxx.com');
INSERT INTO user_role(user_id, role_id) VALUES(1,1);
INSERT INTO user_role(user_id, role_id) VALUES(2,2);
INSERT INTO user_role(user_id, role_id) VALUES(3,3);
INSERT INTO user_role(user_id, role_id) VALUES(4,1);
CREATE TABLE oauth_client_details (
client_id VARCHAR(256) PRIMARY KEY,
resource_ids VARCHAR(256),
client_secret VARCHAR(256),
scope VARCHAR(256),
authorized_grant_types VARCHAR(256),
web_server_redirect_uri VARCHAR(256),
authorities VARCHAR(256),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additional_information VARCHAR(4096),
autoapprove VARCHAR(256)
);
INSERT INTO oauth_client_details
(client_id, client_secret, scope, authorized_grant_types,
authorities, access_token_validity, refresh_token_validity)
VALUES
('avmaintwebsitejwtclientid', '$2y$12$pJ6DTB2Qu9FgK4V9ai38Z.N49faYX04HqO6K/Jz7eeE8r5BjrIZOe', 'read,write,trust', 'password,refresh_token',
'ROLE_CLIENT,ROLE_TRUSTED_CLIENT', 900, 2592000);
最后,这是功能测试,表明甚至/ oauth / token端点都被阻止了:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("embedded")
@TestPropertySource("/application-embedded.yml")
@AutoConfigureMockMvc
public class AccessControllerFunctionalTest {
@Autowired
private WebApplicationContext context;
@Autowired
private MockMvc mvc;
@MockBean
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
@Test
public void doAuthorization() throws Exception {
// I need this for the moment, because it looks like the context isn't loading properly.
when(userRepository.findByUsername(eq("xxxxxxx@gmail.com"))).thenReturn(User.of(
"rmjcoxon@gmail.com",
passwordEncoder.encode("password")));
String requestJson = new ObjectMapper().writeValueAsString(User.of(
"xxxxxxx@gmail.com",
"password"));
String requestString = "username=xxxxxxxx@gmail.com&password=" + passwordEncoder.encode("password") + "&grant_type=password";
mvc.perform(post("/oauth/token").contentType(APPLICATION_FORM_URLENCODED)
.content(requestString)
.with(httpBasic("avmaintwebsitejwtclientid",passwordEncoder.encode("XY7kmzoNzl100"))))
.andDo(print()).andExpect(status().isOk())
.andExpect(content().string(containsString("{\"payload\":\"ok\",\"code\":\"OK\"}")));
}
}
这是我几乎所有内容的错误:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /oauth/token
Parameters = {username=[rmjcoxon@gmail.com], password=[$2a$10$BcQ6RzxSCKjeWWKJDFmUJ.Dj8l6pv7BKu5eXDt5lq/SklUcwS7XfK], grant_type=[password]}
Headers = {Content-Type=[application/x-www-form-urlencoded], Authorization=[Basic YXZtYWludHdlYnNpdGVqd3RjbGllbnRpZDokMmEkMTAkWEFqVzJVUUhUckh4RWlUT0M1RFRndUQ4L2I3UXlFQURuWUdUaFF5dUdqWU9FREFFcVhQb0s=]}
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 401
Error message = Unauthorized
Headers = {WWW-Authenticate=[Basic realm="AVMAINT_REALM"], X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
2018-05-24 19:18:54.640 INFO 15780 --- [ Thread-10] ConfigServletWebServerApplicationContext : Closing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@45bf9982: startup date [Thu May 24 19:18:46 AEST 2018]; root of context hierarchy
2018-05-24 19:18:54.643 INFO 15780 --- [ Thread-10] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
au.com.avmaint.api.access.AccessControllerFunctionalTest > doAuthorization FAILED
Status expected:<200> but was:<401>
Expected :200
Actual :401
<Click to see difference>
java.lang.AssertionError: Status expected:<200> but was:<401>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:55)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:82)
at org.springframework.test.web.servlet.result.StatusResultMatchers.lambda$matcher$9(StatusResultMatchers.java:617)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:178)
at au.com.avmaint.api.access.AccessControllerFunctionalTest.doAuthorization(AccessControllerFunctionalTest.java:80)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
谁能看到我做错了什么?