Spring boot+ mybatis + Spring security 集成 项目使用 Maven 管理依赖
引入 Spring Security 库 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > <version > 2.0.4.RELEASE</version > </dependency > <dependency > <groupId > com.google.guava</groupId > <artifactId > guava</artifactId > <version > 26.0-jre</version > </dependency > <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-lang3</artifactId > <version > 3.8</version > </dependency >
新建 AppUserDetailsAuthenticationProvider 类 类继承 AbstractUserDetailsAuthenticationProvider,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;@Component public final class AppUserDetailsAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {@NonNull @Autowired IAuthenticationService userAuthenticationService; @Override protected void additionalAuthenticationChecks (UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { } @Override protected UserDetails retrieveUser (String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { final Object token = authentication.getCredentials(); return Optional .ofNullable(token) .map(String::valueOf) .flatMap(userAuthenticationService::findByToken) .orElseThrow(() -> new UsernameNotFoundException("Cannot find user with authentication token=" + token)); } }
该类作用 给 spring security 提供一个 service 获取 指定类型’org.springframework.security.core.userdetails.UserDetails’ 的对象,用于检查权限
新建 IAuthenticationService 1 2 3 4 5 6 7 8 9 public interface IAuthenticationService { Optional<String> login (String username, String password) ; Optional<UserDetails> findByToken (String token) ; void logout (UserDetails user) ; }
新建 AuthenticationServiceImpl 实现 IAuthenticationService 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import org.springframework.security.core.userdetails.User;@Service public class AuthenticationServiceImpl implements IAuthenticationService { private static Map<String,UserDetails> users; static { users= new HashMap<String,UserDetails>(); } @Autowired private IUserService userService; @Override public Optional<String> login (String username, String password) { String token = null ; Integer userID = userService.login(username, password); if (Util.isNotNull(userID)) { String uuid = UUID.randomUUID().toString(); token = uuid + username; List<String> roles = userService.queryRoles(userID); String[] rolesTemp = roles.toArray(new String[roles.size()]); UserDetails userDetails = User.withUsername(username).password(password).roles(rolesTemp).build(); users.put(token, userDetails); } return Optional.ofNullable(token); } @Override public Optional<UserDetails> findByToken (String token) { return Optional.ofNullable(users.get(token)); } @Override public void logout (UserDetails user) { } }
注意: UserDetails, 使用 org.springframework.security.core.userdetails.User 类 build() 方法生成
新建 NoRedirectStrategy 1 2 3 4 5 6 7 8 9 10 11 import org.springframework.security.web.RedirectStrategy;public class NoRedirectStrategy implements RedirectStrategy { @Override public void sendRedirect (HttpServletRequest arg0, HttpServletResponse arg1, String arg2) throws IOException { } }
新建 AppAuthenticationProcessingFilter 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;import static com.google.common.net.HttpHeaders.AUTHORIZATION;public final class AppAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter { private static final String BEARER = "Bearer" ; protected AppAuthenticationProcessingFilter (RequestMatcher requiresAuthenticationRequestMatcher) { super (requiresAuthenticationRequestMatcher); } @Override public Authentication attemptAuthentication (HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { final String param = ofNullable(request.getHeader(AUTHORIZATION)).orElse("" ); final String token = ofNullable(param).map(value -> removeStart(value, BEARER)).map(String::trim) .orElseThrow(() -> new BadCredentialsException("Missing Authentication Token" )); final Authentication auth = new UsernamePasswordAuthenticationToken(token, token); return getAuthenticationManager().authenticate(auth); } @Override protected void successfulAuthentication (HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { super .successfulAuthentication(request, response, chain, authResult); chain.doFilter(request, response); } }
新建 AppBasicAuthenticationEntryPoint 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class AppBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint { @Override public void commence (HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { super .commence(request, response, authException); } @Override public void afterPropertiesSet () throws Exception { this .setRealmName("TEST_REALM" ); super .afterPropertiesSet(); } }
认证服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity (prePostEnabled = true )class AppSecurityConfigurer extends WebSecurityConfigurerAdapter { private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(new AntPathRequestMatcher("/public/**" ), new AntPathRequestMatcher("/user/login" )); private static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS); AppUserDetailsAuthenticationProvider provider; AppSecurityConfigurer(final AppUserDetailsAuthenticationProvider provider) { super (); this .provider = requireNonNull(provider); } @Override protected void configure (final AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(provider); } @Override public void configure (final WebSecurity web) { web.ignoring().requestMatchers(PUBLIC_URLS); } @Override protected void configure (final HttpSecurity http) throws Exception { String[] putAntPatternsByAdmin = { "/products/{id}" }; String[] deleteAntPatternsByAdmin = { "/products/**" }; http.sessionManagement().sessionCreationPolicy(STATELESS).and().exceptionHandling() .defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), PROTECTED_URLS).and() .authorizeRequests().antMatchers(HttpMethod.PUT, putAntPatternsByAdmin).hasRole("ADMIN" ).and() .authorizeRequests().antMatchers(HttpMethod.DELETE, deleteAntPatternsByAdmin).hasRole("ADMIN" ).and() .authenticationProvider(provider) .addFilterBefore(restAuthenticationFilter(), AnonymousAuthenticationFilter.class ).authorizeRequests () // cors .requestMatchers (PROTECTED_URLS ).authenticated ().and ().cors ().and ().csrf ().disable ().formLogin () .disable () // http 认证 .httpBasic().realmName("TEST_REALM").authenticationEntryPoint(getBasicAuthEntryPoint()) .and().logout().disable(); } @Bean AppAuthenticationProcessingFilter restAuthenticationFilter () throws Exception { final AppAuthenticationProcessingFilter filter = new AppAuthenticationProcessingFilter(PROTECTED_URLS); filter.setAuthenticationManager(authenticationManager()); filter.setAuthenticationSuccessHandler(successHandler()); return filter; } @Bean SimpleUrlAuthenticationSuccessHandler successHandler () { final SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler(); successHandler.setRedirectStrategy(new NoRedirectStrategy()); return successHandler; } @Bean FilterRegistrationBean disableAutoRegistration (final AppAuthenticationProcessingFilter filter) { final FilterRegistrationBean registration = new FilterRegistrationBean(filter); registration.setEnabled(false ); return registration; } @Bean AuthenticationEntryPoint forbiddenEntryPoint () { return new HttpStatusEntryPoint(FORBIDDEN); } @Bean CorsConfigurationSource corsConfigurationSource () { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("*" )); configuration.setAllowedMethods(Arrays.asList("*" )); configuration.setAllowedHeaders(Arrays.asList("*" )); configuration.setAllowCredentials(true ); long maxAge = 60 ; configuration.setMaxAge(maxAge); configuration.setExposedHeaders(Arrays.asList("Authorization" , "Content-Type" )); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**" , configuration); return source; } @Bean public AppBasicAuthenticationEntryPoint getBasicAuthEntryPoint () { return new AppBasicAuthenticationEntryPoint(); } }