我正在使用Auth0来保护我的API,这非常有效.我的设置& config与Auth0文档中的suggested setup相同:
// SecurityConfig.java @Configuration @EnableWebSecurity(debug = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { // auth0 config vars here @Override protected void configure(HttpSecurity http) { JwtWebSecurityConfigurer .forRS256(apiAudience,issuer) .configure(http) .authorizeRequests() .antMatchers(HttpMethod.GET,"/api/public").permitAll() .antMatchers(HttpMethod.GET,"/api/private").authenticated(); } }
使用此设置,spring安全主体从jwt标记设置为userId(sub):auth0 | 5b2b ….但是,我希望它设置为匹配用户(来自我的数据库)而不仅仅是userId .我的问题是如何做到这一点.
我试过的
我已经尝试实现从this tutorial复制的自定义数据库支持的UserDetailsService.
然而,无论我如何尝试将其添加到我的conf中,它都不会被调用.我尝试过几种不同的方式添加它没有效果:
// SecurityConfig.java (changes only) // My custom userDetailsService,overriding the loadUserByUsername // method from Spring Framework's UserDetailsService. @Autowired private MyUserDetailsService userDetailsService; protected void configure(HttpSecurity http) { http.userDetailsService(userDetailsService); // Option 1 http.authenticationProvider(authenticationProvider()); // Option 2 JwtWebSecurityConfigurer [...] // The rest unchanged from above } @Override // Option 3 & 4: Override the following method protected void configure(AuthenticationManagerBuilder auth) { auth.authenticationProvider(authenticationProvider()); // Option 3 auth.userDetailsService(userDetailsService); // Option 4 } @Bean // Needed for Options 2 or 4 public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService); return authProvider; }
不幸的是,由于我需要将其与Auth0身份验证相结合,所以没有类似的“userDetails not called called”问题帮助了我.
我并不认为我正走在正确的道路上.我觉得很奇怪,在这个极为常见的用例中我找不到Auth0的任何文档,所以也许我错过了一些明显的东西.
PS:不确定是否相关,但在init期间始终记录以下内容.
Jun 27,2018 11:25:22 AM com.test.UserRepository initDao INFO: No authentication manager set. Reauthentication of users when changing passwords will not be performed.
编辑1:
根据Ashish451的回答,我尝试复制他的CustomUserDetailsService,并将以下内容添加到我的SecurityConfig中:
@Autowired private CustomUserDetailsService userService; @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Autowired public void configureGlobal( AuthenticationManagerBuilder auth ) throws Exception { auth.userDetailsService( userService ); }
不幸的是,通过这些更改,仍未调用CustomUserDetailsService.
编辑2:
添加@Norberto Ritzmann建议的日志记录方法时的输出:
Jul 04,2018 3:49:22 PM com.test.repositories.UserRepositoryImpl initDao INFO: No authentication manager set. Reauthentication of users when changing passwords will not be performed. Jul 04,2018 3:49:22 PM com.test.runner.JettyRunner testUserDetailsImpl INFO: UserDetailsService implementation: com.test.services.CustomUserDetailsService
解决方法
我不知道什么是apiAudience,发行人,但它产生了JWT的子猜测.
您的问题是您希望根据数据库更改JWT子.
我最近在Spring Boot Application中实现了JWT安全性.
//我的适配器类和你的一样,除了我添加过滤器的一件事.在此过滤器中,我正在验证JWT令牌.每次触发安全休息URL时都会调用此过滤器.
import java.nio.charset.StandardCharsets; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.thymeleaf.spring5.SpringTemplateEngine; import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver; import com.dev.myapp.jwt.model.CustomUserDetailsService; import com.dev.myapp.security.RestAuthenticationEntryPoint; import com.dev.myapp.security.TokenAuthenticationFilter; import com.dev.myapp.security.TokenHelper; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Autowired private CustomUserDetailsService jwtUserDetailsService; // Get UserDetail bu UserName @Autowired private RestAuthenticationEntryPoint restAuthenticationEntryPoint; // Handle any exception during Authentication @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } // Binds User service for User and Password Query from Database with Password Encryption @Autowired public void configureGlobal( AuthenticationManagerBuilder auth ) throws Exception { auth.userDetailsService( jwtUserDetailsService ) .passwordEncoder( passwordEncoder() ); } @Autowired TokenHelper tokenHelper; // Contains method for JWT key Generation,Validation and many more... @Override protected void configure(HttpSecurity http) throws Exception { http .sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS ).and() .exceptionHandling().authenticationEntryPoint( restAuthenticationEntryPoint ).and() .authorizeRequests() .antMatchers("/auth/**").permitAll() .anyRequest().authenticated().and() .addFilterBefore(new TokenAuthenticationFilter(tokenHelper,jwtUserDetailsService),BasicAuthenticationFilter.class); http.csrf().disable(); } // Patterns to ignore from JWT security check @Override public void configure(WebSecurity web) throws Exception { // TokenAuthenticationFilter will ignore below paths web.ignoring().antMatchers( HttpMethod.POST,"/auth/login" ); web.ignoring().antMatchers( HttpMethod.GET,"/","/assets/**","/*.html","/favicon.ico","/**/*.html","/**/*.css","/**/*.js" ); } }
@Transactional @Repository public class CustomUserDetailsService implements UserDetailsService { protected final Log LOGGER = LogFactory.getLog(getClass()); @Autowired private UserRepo userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User uu = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException(String.format("No user found with username '%s'.",username)); } else { return user; } } }
//未经授权的访问处理程序
@Component public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request,HttpServletResponse response,AuthenticationException authException) throws IOException { // This is invoked when user tries to access a secured REST resource without supplying any credentials response.sendError(HttpServletResponse.SC_UNAUTHORIZED,authException.getMessage()); } }
//用于验证JWT令牌的过滤链
import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.web.filter.OncePerRequestFilter; public class TokenAuthenticationFilter extends OncePerRequestFilter { protected final Log logger = LogFactory.getLog(getClass()); private TokenHelper tokenHelper; private UserDetailsService userDetailsService; public TokenAuthenticationFilter(TokenHelper tokenHelper,UserDetailsService userDetailsService) { this.tokenHelper = tokenHelper; this.userDetailsService = userDetailsService; } @Override public void doFilterInternal( HttpServletRequest request,FilterChain chain ) throws IOException,ServletException { String username; String authToken = tokenHelper.getToken(request); logger.info("AuthToken: "+authToken); if (authToken != null) { // get username from token username = tokenHelper.getUsernameFromToken(authToken); logger.info("UserName: "+username); if (username != null) { // get user UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (tokenHelper.validateToken(authToken,userDetails)) { // create authentication TokenBasedAuthentication authentication = new TokenBasedAuthentication(userDetails); authentication.setToken(authToken); SecurityContextHolder.getContext().setAuthentication(authentication); // Adding Token in Security COntext } }else{ logger.error("Something is wrong with Token."); } } chain.doFilter(request,response); } }
// TokenBasedAuthentication类
import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.userdetails.UserDetails; public class TokenBasedAuthentication extends AbstractAuthenticationToken { private static final long serialVersionUID = -8448265604081678951L; private String token; private final UserDetails principle; public TokenBasedAuthentication( UserDetails principle ) { super( principle.getAuthorities() ); this.principle = principle; } public String getToken() { return token; } public void setToken( String token ) { this.token = token; } @Override public boolean isAuthenticated() { return true; } @Override public Object getCredentials() { return token; } @Override public UserDetails getPrincipal() { return principle; } }
// JWT生成和验证逻辑的助手类
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import com.dev.myapp.common.TimeProvider; import com.dev.myapp.entity.User; @Component public class TokenHelper { protected final Log LOGGER = LogFactory.getLog(getClass()); @Value("${app.name}") // reading details from property file added in Class path private String APP_NAME; @Value("${jwt.secret}") public String SECRET; @Value("${jwt.licenseSecret}") public String LICENSE_SECRET; @Value("${jwt.expires_in}") private int EXPIRES_IN; @Value("${jwt.mobile_expires_in}") private int MOBILE_EXPIRES_IN; @Value("${jwt.header}") private String AUTH_HEADER; @Autowired TimeProvider timeProvider; // return current time. Basically Deployment time. private SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS512; // Generate Token based on UserName. You can Customize this public String generateToken(String username) { String audience = generateAudience(); return Jwts.builder() .setIssuer( APP_NAME ) .setSubject(username) .setAudience(audience) .setIssuedAt(timeProvider.now()) .setExpiration(generateExpirationDate()) .signWith( SIGNATURE_ALGORITHM,SECRET ) .compact(); } public Boolean validateToken(String token,UserDetails userDetails) { User user = (User) userDetails; final String username = getUsernameFromToken(token); final Date created = getIssuedAtDateFromToken(token); return ( username != null && username.equals(userDetails.getUsername()) ); } // If Token is valid will extract all claim else throw appropriate error private Claims getAllClaimsFromToken(String token) { Claims claims; try { claims = Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); } catch (Exception e) { LOGGER.error("Could not get all claims Token from passed token"); claims = null; } return claims; } private Date generateExpirationDate() { long expiresIn = EXPIRES_IN; return new Date(timeProvider.now().getTime() + expiresIn * 1000); } }
对于这个日志
No authentication manager set. Reauthentication of users when changing passwords
由于尚未使用Name loadUserByUsername实现方法.你得到这个日志.
编辑1:
我正在使用Filter Chain来验证令牌并在安全上下文中添加用户,这将从令牌中提取….
使用JWT并且您使用的是AuthO,只有实现不同.我为完整的工作流程添加了完整的实施.
您专注于从WebSecurityConfig类实现authenticationManagerBean和configureGlobal以使用UserService.
和TokenBasedAuthentication类实现.
其他你可以跳过的东西.