I have a RESTful Spring Boot web service, that is to be consumed from two different types of clients (Angularjs and Android). I am trying to implement a Spring Oauth2 authentication for an Android client and CSRF tokens for the Angular web client.
Is it possible to implement two of these security implementations side by side and have Spring check if an authentication request matches either of the options? If so, what am I doing wrong with the code below?
#Configuration
public class ServerConfiguration {
#Configuration
#Order(1)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
protected AuthenticationManager authenticationManager() {
return authenticationManager;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/index.html", "/home.html", "/login.html", "/", "/oauth/token").permitAll().anyRequest()
.authenticated()
.and()
.csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
private static final String RESOURCE_ID = "council-alert-oauth";
#Configuration
#EnableResourceServer
#Order(2)
protected static class ResourceServerConfiguration extends
ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
// #formatter:off
resources
.resourceId(RESOURCE_ID);
// #formatter:on
}
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/report/**").access("#oauth2.hasScope('write')")
.antMatchers("/api/user/**").access("#oauth2.hasScope('write')")
.antMatchers("/api/employee/**").access("#oauth2.hasScope('write')")
.antMatchers("/api/citizen/report").access("#oauth2.hasScope('write')");
// #formatter:on
}
}
#Configuration
#EnableAuthorizationServer
#Order(2)
protected static class AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter {
private TokenStore tokenStore = new InMemoryTokenStore();
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
// #formatter:off
endpoints
.tokenStore(this.tokenStore)
.authenticationManager(this.authenticationManager);
// #formatter:on
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// #formatter:off
clients
.inMemory()
/*
.withClient("angular-client")
.authorizedGrantTypes("password", "refresh_token")
.authorities("ADMIN")
.scopes("read", "write", "trust")
.resourceIds(RESOURCE_ID)
.secret("council-alert-angular-secret")
//.redirectUris("http://localhost:8080/")
.autoApprove(true)
.and()*/
.withClient("android-client")
.resourceIds(RESOURCE_ID)
.authorizedGrantTypes("password", "refresh_token")
.authorities("ADMIN")
.scopes("read", "write")
.secret("council-alert-android-secret")
.and()
.withClient("android-client")
.resourceIds(RESOURCE_ID)
.authorizedGrantTypes("password", "refresh_token")
.authorities("USER")
.scopes("read", "write")
.secret("council-alert-android-secret");
// #formatter:on
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(this.tokenStore);
return tokenServices;
}
}
}
Related
I'm suffering for multiple login processing.
I've searched a lot about it and none of answers out there are working.
I wrote two login processing one for admin and the other one for normal user.
And also wrote each success and failure handlers but the handlers are always working with the last one in #order(2) configuration even though I request to /admin.
My problems are:
I can login but the success handler is always triggered as the last one in #order(2).
The failure handler is triggered but as the last one as well regardless what URL I request and it throws 404 error (I can see it goes on proper controller during debugging). Maybe tiles doesn't work on failure process?
Here is my security config:
#Configuration
#EnableWebSecurity
#AllArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private UsersServiceImpl usersService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/css/**",
"/js/**",
"/img/**",
"/font/**",
"/html/**",
"/jusoPopup",
"favicon.ico"
);
}
#Configuration
#Order(1)
#NoArgsConstructor
public static class AdminConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/admin/**")
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/admin/login")
.defaultSuccessUrl("/admin")
.failureHandler(adminFailureHandler())
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.and()
.csrf().disable();
}
#Bean
public AuthenticationSuccessHandler adminSuccessHandler() {
return new CustomLoginSuccessHandler("/admin");
}
#Bean
public AuthenticationFailureHandler adminFailureHandler() {
return new CustomLoginFailureHandler("/admin/login?error=true");
}
}
#Configuration
#Order(2)
#NoArgsConstructor
public static class NormalConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/Ticketing/**", "/**/write").hasRole("MEMBER")
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.successHandler(successHandler())
.failureHandler(failureHandler())
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.and()
.headers()
.frameOptions().sameOrigin()
.and()
.csrf().disable();
}
#Bean
public AuthenticationSuccessHandler successHandler() {
return new CustomLoginSuccessHandler("/");
}
#Bean
public AuthenticationFailureHandler failureHandler() {
return new CustomLoginFailureHandler("/login?error=true");
}
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(usersService).passwordEncoder(passwordEncoder());
}
}
class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
public CustomLoginSuccessHandler(String defaultTargetUrl) {
setDefaultTargetUrl(defaultTargetUrl);
}
#Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication
) throws ServletException, IOException {
HttpSession session = request.getSession();
if (session != null) {
String redirectUrl = (String) session.getAttribute("prevPage");
if (redirectUrl != null) {
session.removeAttribute("prevPage");
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
}
}
#Getter
#Setter
#AllArgsConstructor
class CustomLoginFailureHandler implements AuthenticationFailureHandler {
private String defaultFailureUrl;
#Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception
) throws IOException, ServletException {
String errorMessage = "some error message";
request.setAttribute("errorMessage", errorMessage);
request.getRequestDispatcher(defaultFailureUrl).forward(request, response);
}
}
It's hard to find proper working well multiple login processing resource out there if you guys know the good resource about it please let me know.
I hope it's my code error so that I don't have to change the application structure.
I read some Baeldung guide and spring security documents Here is my edited configuration :
#EnableWebSecurity
public class SecurityConfig{
#NoArgsConstructor
#Configuration
#Order(1)
public static class AdminConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/admin/**")
.authorizeRequests().anyRequest().hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/admin/login")
.defaultSuccessUrl("/admin")
.failureHandler(adminFailureHandler())
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.and()
.csrf().disable();
}
#Bean
public AuthenticationEntryPoint authenticationEntryPoint(){
BasicAuthenticationEntryPoint entryPoint =
new BasicAuthenticationEntryPoint();
entryPoint.setRealmName("admin realm");
return entryPoint;
}
#Bean
public AuthenticationFailureHandler adminFailureHandler() {
return new CustomLoginFailureHandler("/admin/login");
}
}
#AllArgsConstructor
#Configuration
public static class NormalConfigurationAdapter extends WebSecurityConfigurerAdapter {
private UsersServiceImpl usersService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers(
"/css/**",
"/js/**",
"/img/**",
"/font/**",
"/html/**",
"/jusoPopup",
"favicon.ico"
);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/Ticketing/**", "**/write")
.hasRole("MEMBER")
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.successHandler(successHandler())
.failureHandler(failureHandler())
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.and()
.headers().frameOptions().sameOrigin()
.and()
.csrf().disable();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(usersService).passwordEncoder(passwordEncoder());
}
#Bean
public AuthenticationSuccessHandler successHandler() {
return new CustomLoginSuccessHandler("/");
}
#Bean
public AuthenticationFailureHandler failureHandler() {
return new CustomLoginFailureHandler("/login");
}
}
}
class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
public CustomLoginSuccessHandler(String defaultTargetUrl) {
setDefaultTargetUrl(defaultTargetUrl);
}
#Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication
) throws ServletException, IOException {
HttpSession session = request.getSession();
if (session != null) {
String redirectUrl = (String) session.getAttribute("prevPage");
if (redirectUrl != null) {
session.removeAttribute("prevPage");
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
}
}
#Getter
#Setter
#AllArgsConstructor
class CustomLoginFailureHandler implements AuthenticationFailureHandler {
private String defaultFailureUrl;
#Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception
) throws IOException, ServletException {
String errorMessage = "Error";
request.setAttribute("errorMessage", errorMessage);
request.getRequestDispatcher(defaultFailureUrl).forward(request, response);
}
}
It looks more organized than before i guess but still has the same problem... also read about AuthenticationEntryPoint but not sure it's fit in my case
I try to generate a JWT Token with Spring Security but I don't how to do it correctly (with the best practice).
Do I should "intercept" the authenticate method within UsernamePasswordAuthenticationFilter somewhere and generate token inside it ?
Or it is better to use AuthenticationManager autowired in the controller '/login' ?
I'm afraid to authenticate the user twice if I use the controller mechanism.
I used this tutorial : tutorial Jwt Token
Here is my code :
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserService userService;
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
// #Autowired
// private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtTokenFilter jwtTokenFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/css/**", "/login/**", "/register/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
//.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
.formLogin()
.usernameParameter("email")
//.loginPage("http://localhost:4200/login").failureUrl("/login-error")
.and()
.logout()
.permitAll();
http
.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public CustomDaoAuthenticationProvider authenticationProvider() {
CustomDaoAuthenticationProvider authenticationProvider = new CustomDaoAuthenticationProvider();
authenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
authenticationProvider.setUserDetailsService(userService);
return authenticationProvider;
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebConfig() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(
"http://localhost:4200")
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS")
.allowedHeaders("Content-Type", "X-Requested-With", "accept", "Origin", "Access-Control-Request-Method",
"Access-Control-Request-Headers", "Authorization", "Cache-Control",
"Access-Control-Allow-Origin")
.exposedHeaders("Access-Control-Allow-Origin", "Access-Control-Allow-Credentials")
.allowCredentials(true).maxAge(3600);
}
};
}
}
Token Filter
public class JwtTokenFilter extends GenericFilterBean {
private JwtTokenProvider jwtTokenProvider;
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
String token = jwtTokenProvider.resolveToken((HttpServletRequest) req);
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = token != null ? jwtTokenProvider.getAuthentication(token) : null;
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(req, res);
}
}
Token Provider
#Component
public class JwtTokenProvider {
#Value("${security.jwt.token.secret-key:secret}")
private String secretKey = "secret";
#Value("${security.jwt.token.expire-length:3600000}")
private long validityInMilliseconds = 3600000; // 1h
#Autowired
private UserDetailsService userDetailsService;
#PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
public String createToken(String username, List<String> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", roles);
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
return Jwts.builder()//
.setClaims(claims)//
.setIssuedAt(now)//
.setExpiration(validity)//
.signWith(SignatureAlgorithm.HS256, secretKey)//
.compact();
}
public Authentication getAuthentication(String token) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(getUsername(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
public String getUsername(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
public String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
if (claims.getBody().getExpiration().before(new Date())) {
return false;
}
return true;
} catch (JwtException | IllegalArgumentException e) {
throw new InvalidJwtAuthenticationException("Expired or invalid JWT token");
}
}
}
Given your context, the controller is responsible for issuing a new token (after validating credentials) while the filter is responsible for authenticating the user against the given token. The controller should not populate the security context (authenticate user), it is the filter's responsibility.
To better understand the two phases:
Spring uses two filters to authenticate and log in a user.
See UsernamePasswordAuthenticationFilter and SecurityContextPersistenceFilter in a "username/password" scenario, from the Spring Security project: the first one processes an authentication attempt (username/password) while the latter populates the security context from a SecurityContextRepository (from a session in general).
I want to execute code on successful authentication to add certain roles to my user object:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${ad.domain}")
private String AD_DOMAIN;
#Value("${ad.url}")
private String AD_URL;
#Autowired
UserRoleComponent userRoleComponent;
private final Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
protected void configure(HttpSecurity http) throws Exception {
this.logger.info("Verify logging level"); //works
http
.authorizeRequests()
.anyRequest()
.fullyAuthenticated()
.and()
.formLogin()
.successHandler(new AuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
userRoleComponent.testIt();
redirectStrategy.sendRedirect(request, response, "/");
}
})
.and()
.httpBasic();
http.formLogin().defaultSuccessUrl("/", true);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(AD_DOMAIN,
AD_URL);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}
So I tried to add a new AuthenticationSuccessHandler to insert code to be executed in a component (this is where the roles should be added then).
In my UserRoleController:
#Component
public class UserRoleComponent {
private Logger logger = LoggerFactory.getLogger(UserRoleComponent.class);
public void testIt() {
this.logger.info("=============== User Authentication ==============");
System.out.println("================ User Authentication ================");
}
}
Now sadly when I login successfully there is no output printed. How can I solve this issue?
Edit: I verified the loggin level is correct and the system.out.println does not work also.
I use postMan, enter the request address http://localhost:8011/umrah/oauth/token?client_id=client_2&username=1234567&password=123456&grant_type=password&client_secret=123456, click the send button, an error occurs,It works fine in memory, when I want to use Jdbc token storage,Idea console error: failed to find access token for token,I found some information and did not find a suitable solution.
POSTMAN request params
oauth_client_token table is null
console error
#Configuration
#EnableWebSecurity
#Slf4j
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Resource(name = "userService")
private UserService userService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.logout()
.clearAuthentication(true)
.and()
.requestMatchers().anyRequest()
.and()
.authorizeRequests()
.antMatchers("/oauth/*", "/webjars/**", "/resources/**", "/swagger-ui.html"
, "/swagger-resources/**", "/v2/api-docs", "index.html", "/logout"
, "/swagger","/user/loginIn").permitAll()
.and()
.csrf()
.disable();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
#Configuration
public class OAuth2ServerConfig {
private static final String DEMO_RESOURCE_ID = "order";
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(DEMO_RESOURCE_ID).stateless(true);
}
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.logout()
.clearAuthentication(true)
.and()
// Since we want the protected resources to be accessible in the UI as well we need
// session creation to be allowed (it's disabled by default in 2.0.6)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
// .and()
// .requestMatchers().anyRequest()
.and()
.anonymous()
.and()
.authorizeRequests()
.antMatchers("/base/**", "/oauth/*", "/webjars/**", "/resources/**", "/swagger-ui.html"
, "/swagger-resources/**", "/v2/api-docs", "index.html", "/swagger/**","/user/loginIn").permitAll()
.anyRequest().authenticated()
.and()
.cors()
.and()
.csrf()
.disable();//配置order访问控制,必须认证过后才可以访问
// #formatter:on
}
}
#Configuration
#EnableAuthorizationServer
#Slf4j
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
AuthenticationManager authenticationManager;
#Autowired
RedisConnectionFactory redisConnectionFactory;
#Autowired
UserDetailsService userDetailsService;
// #Autowired
// #Qualifier("myMemoryTokenStore")
// TokenStore myTokenStore;
#Autowired
private DataSource dataSource;
#Bean // 声明TokenStore实现
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//配置两个客户端,一个用于password认证一个用于client认证
// clients.inMemory().withClient("client_1")
//// .resourceIds(DEMO_RESOURCE_ID)
// .authorizedGrantTypes("client_credentials")
// .scopes("select")
// .authorities("ROLE_ADMIN","ROLE_USER")
// .secret("123456")
// .and().withClient("client_2")
//// .resourceIds(DEMO_RESOURCE_ID)
// .authorizedGrantTypes("password", "refresh_token")
// .scopes("select")
// .accessTokenValiditySeconds(1800)
// .refreshTokenValiditySeconds(3600)
// .authorities("ROLE_ADMIN","ROLE_USER")
// .secret("123456");
clients.withClientDetails(clientDetails());
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore())
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
// 2018-4-3 增加配置,允许 GET、POST 请求获取 token,即访问端点:oauth/token
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
// 配置TokenServices参数
DefaultTokenServices tokenServices = (DefaultTokenServices) endpoints.getDefaultAuthorizationServerTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setSupportRefreshToken(true);
// 复用refresh token
tokenServices.setReuseRefreshToken(true);
tokenServices.setRefreshTokenValiditySeconds(3600);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1)); // 1天
endpoints.tokenServices(tokenServices);
super.configure(endpoints);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
//允许表单认证
oauthServer.allowFormAuthenticationForClients();
}
}
#FrameworkEndpoint
public class LogoutEndpoint {
#Qualifier("myMemoryTokenStore")
#Autowired
private TokenStore tokenStore;
#RequestMapping(value = "/oauth/logout", method= RequestMethod.POST)
#ResponseStatus(HttpStatus.OK)
public void logout(HttpServletRequest request, HttpServletResponse response){
String authHeader = request.getHeader("Authorization");
if (authHeader != null) {
String tokenValue = authHeader.replace("Bearer", "").trim();
OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);
tokenStore.removeAccessToken(accessToken);
}
}
}
}
#Service("userService")
#Slf4j
public class UserService implements UserDetailsService {
#Resource(name = "service.UserService")
private com.jolly.atplan.umrah.service.service.UserService userService;
#Override
public UserDetails loadUserByUsername(String loginId) throws UsernameNotFoundException {
log.info("LoginID : {}",loginId);
User user = userService.getUserByLoginId(loginId);
if(Objects.isNull(user)){
throw new UsernameNotFoundException("User " + loginId + " was not found in the database");
}
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
// List<UserAuthority> authorityList = userAuthorityDao.getAuthorityListByUser(loginId);
// for (UserAuthority authority : authorityList) {
// GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authority.getAuthority());
// grantedAuthorities.add(grantedAuthority);
// }
//返回一个SpringSecurity需要的用户对象
return new org.springframework.security.core.userdetails.User(
user.getLoginId(),
user.getPwd(),
grantedAuthorities);
}
}
already at work,I override the JdbcTokenStore readAccessToken method,Thanks Rest service with oauth2: Failed to find access token for token
`public class JdbcTokenStores extends JdbcTokenStore {
private static final Log LOG = LogFactory.getLog(JdbcTokenStores.class);
public JdbcTokenStores(DataSource dataSource) {
super(dataSource);
}
#Override
public OAuth2AccessToken readAccessToken(String tokenValue) {
OAuth2AccessToken accessToken = null;
try {
accessToken = new DefaultOAuth2AccessToken(tokenValue);
}
catch (EmptyResultDataAccessException e) {
if (LOG.isInfoEnabled()) {
LOG.info("Failed to find access token for token "+tokenValue);
}
}
catch (IllegalArgumentException e) {
LOG.warn("Failed to deserialize access token for " +tokenValue,e);
removeAccessToken(tokenValue);
}
return accessToken;
}
}`
We are setting up Spring Security within a 1.3 Spring Boot application. We have created a class to configure everything with Java config but for some reason every time I try to access to any of the URLS that are configured to "permitAll()" I get a message response similar to this one:
{
"timestamp": 1443099232454,
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/api/register"
}
I am not really sure why I get this if I am setting up the antMatchers to allow access to the registration, authentication and activation urls. If I disable those three lines I am able to access these three endpoints.
This is my current configuration:
SecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Inject
private Http401UnauthorizedEntryPoint authenticationEntryPoint;
#Inject
private UserDetailsService userDetailsService;
#Inject
private TokenProvider tokenProvider;
public SecurityConfig() {
super(true);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.csrf()
.disable()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.apply(securityConfigurerAdapter());
// #formatter:on
}
private JwtTokenConfigurer securityConfigurerAdapter() {
return new JwtTokenConfigurer(tokenProvider);
}
}
UserDetailsService.java
#Service("userDetailsService")
#Log4j2
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
#Inject
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(final String email) {
log.debug("Authenticating {}", email);
String lowercaseEmail = email.toLowerCase();
Optional<User> userFromDatabase = userRepository.findOneByEmail(lowercaseEmail);
return userFromDatabase.map(
user -> {
if (!user.isEnabled()) {
throw new DisabledException("User " + lowercaseEmail + " is disabled");
}
List<GrantedAuthority> grantedAuthorities = user.getRoles().stream()
.map(role -> role.getGrantedAuthority()).collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(lowercaseEmail, user.getPassword(),
grantedAuthorities);
}).orElseThrow(
() -> new UsernameNotFoundException("User " + lowercaseEmail + " was not found in the database"));
}
}
JwtTokenConfigurer.java
public class JwtTokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private TokenProvider tokenProvider;
public JwtTokenConfigurer(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
#Override
public void configure(HttpSecurity http) throws Exception {
JwtTokenFilter customFilter = new JwtTokenFilter(tokenProvider);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
JwtTokenFilter.java
public class JwtTokenFilter extends GenericFilterBean {
private final static String JWT_TOKEN_HEADER_NAME = "Authorization";
private TokenProvider tokenProvider;
public JwtTokenFilter(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
try {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String jwtToken = httpServletRequest.getHeader(JWT_TOKEN_HEADER_NAME);
if (StringUtils.hasText(jwtToken)) {
String authorizationSchema = "Bearer";
if (jwtToken.indexOf(authorizationSchema) == -1) {
throw new InsufficientAuthenticationException("Authorization schema not found");
}
jwtToken = jwtToken.substring(authorizationSchema.length()).trim();
JwtClaims claims = tokenProvider.parseToken(jwtToken);
String email = (String) claims.getClaimValue(TokenConstants.EMAIL.name());
List<GrantedAuthority> grantedAuthorities = claims.getStringListClaimValue(TokenConstants.ROLES.name())
.stream().map(role -> new SimpleGrantedAuthority(role)).collect(Collectors.toList());
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
email, null, grantedAuthorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
filterChain.doFilter(servletRequest, servletResponse);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
Http401UnauthorizedEntryPoint.java
#Component
public class Http401UnauthorizedEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg2)
throws IOException, ServletException {
log.debug("Pre-authenticated entry point called. Rejecting access");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied");
}
}
As I mentioned before, every time I try to access any of these three endpoints:
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").permitAll()
I get access denied... Any ideas?
You need to allow anonymous users.
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.csrf()
.disable()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").permitAll()
.and()
.anonymous()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.apply(securityConfigurerAdapter());
// #formatter:on
}
Because AbstractSecurityInterceptor always asks if there is something in the SecurityContextHolder.
AbstractSecurityInterceptor#beforeInvocation line 221
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound","An Authentication object was not found in the SecurityContext"),object, attributes);
}