Spring boot+ mybatis + Spring security 集成

项目使用 Maven 管理依赖

引入 Spring Security 库

1
<dependency>
2
    <groupId>org.springframework.boot</groupId>
3
    <artifactId>spring-boot-starter-security</artifactId>
4
    <version>2.0.4.RELEASE</version>
5
</dependency>
6
<dependency>
7
	<groupId>com.google.guava</groupId>
8
	<artifactId>guava</artifactId>
9
	<version>26.0-jre</version>
10
</dependency>
11
<dependency>
12
	<groupId>org.apache.commons</groupId>
13
	<artifactId>commons-lang3</artifactId>
14
	<version>3.8</version>
15
</dependency>

新建 AppUserDetailsAuthenticationProvider 类

类继承 AbstractUserDetailsAuthenticationProvider,代码如下:

1
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
2
// 省略其他
3
4
@Component
5
public final class AppUserDetailsAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
6
@NonNull
7
@Autowired
8
IAuthenticationService userAuthenticationService;
9
10
    @Override
11
    protected void additionalAuthenticationChecks(UserDetails userDetails,
12
    		UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
13
    }
14
15
    /**
16
     * 根据token 查出 user对象,提供给spring security 以验证权限
17
     */
18
    @Override
19
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
20
    		throws AuthenticationException {
21
    	final Object token = authentication.getCredentials();
22
    	return Optional
23
    		      .ofNullable(token)
24
    		      .map(String::valueOf)
25
    		      //查出的对象,同时会进行权限判断
26
    		      .flatMap(userAuthenticationService::findByToken)
27
    		      .orElseThrow(() -> new UsernameNotFoundException("Cannot find user with authentication token=" + token));
28
    }
29
30
}

该类作用 给 spring security 提供一个 service 获取 指定类型’org.springframework.security.core.userdetails.UserDetails’ 的对象,用于检查权限

新建 IAuthenticationService

1
public interface IAuthenticationService {
2
3
      // 登入时,build 一个 UserDetails对象,存入 内存/Redis
4
	  Optional<String> login(String username, String password);
5
6
	  Optional<UserDetails> findByToken(String token);
7
8
	  void logout(UserDetails user);
9
}

新建 AuthenticationServiceImpl

实现 IAuthenticationService 接口

1
import org.springframework.security.core.userdetails.User;
2
// ...
3
4
@Service
5
public class AuthenticationServiceImpl implements IAuthenticationService {
6
	/**
7
	 * key : token
8
	 * value: user
9
	 */
10
	private static Map<String,UserDetails> users;
11
	static{
12
		users=  new HashMap<String,UserDetails>();
13
	}
14
15
	@Autowired
16
	private IUserService userService;
17
18
	@Override
19
	public Optional<String> login(String username, String password) {
20
		String token = null;
21
		Integer userID = userService.login(username, password);
22
		if(Util.isNotNull(userID)) {
23
		    String uuid = UUID.randomUUID().toString();
24
		    token = uuid + username;
25
		    List<String>  roles = userService.queryRoles(userID);
26
		    String[] rolesTemp = roles.toArray(new String[roles.size()]);
27
			UserDetails userDetails = User.withUsername(username).password(password).roles(rolesTemp).build();
28
			users.put(token, userDetails);
29
		}
30
		return Optional.ofNullable(token);
31
	}
32
33
	/**
34
	 * 返回一个 spring 用于判断的 userDetials 对象
35
	 */
36
	@Override
37
	public Optional<UserDetails> findByToken(String token) {
38
		return Optional.ofNullable(users.get(token));
39
	}
40
41
	@Override
42
	public void logout(UserDetails user) {
43
	}
44
}

注意: UserDetails, 使用 org.springframework.security.core.userdetails.User 类 build() 方法生成

新建 NoRedirectStrategy

1
import org.springframework.security.web.RedirectStrategy;
2
// ...
3
public class NoRedirectStrategy implements RedirectStrategy {
4
5
	@Override
6
	public void sendRedirect(HttpServletRequest arg0, HttpServletResponse arg1, String arg2) throws IOException {
7
		// TODO Auto-generated method stub
8
9
	}
10
11
}

新建 AppAuthenticationProcessingFilter

1
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
2
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
3
// ...
4
5
public final class AppAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
6
	private static final String BEARER = "Bearer";
7
8
	protected AppAuthenticationProcessingFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
9
		super(requiresAuthenticationRequestMatcher);
10
	}
11
12
	@Override
13
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
14
			throws AuthenticationException, IOException, ServletException {
15
16
		final String param = ofNullable(request.getHeader(AUTHORIZATION)).orElse("");
17
18
		final String token = ofNullable(param).map(value -> removeStart(value, BEARER)).map(String::trim)
19
				.orElseThrow(() -> new BadCredentialsException("Missing Authentication Token"));
20
21
		final Authentication auth = new UsernamePasswordAuthenticationToken(token, token);
22
		return getAuthenticationManager().authenticate(auth);
23
	}
24
25
	@Override
26
	protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
27
			Authentication authResult) throws IOException, ServletException {
28
		super.successfulAuthentication(request, response, chain, authResult);
29
		chain.doFilter(request, response);
30
	}
31
32
}

新建 AppBasicAuthenticationEntryPoint

1
public class AppBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
2
3
4
	@Override
5
	public void commence(HttpServletRequest request, HttpServletResponse response,
6
			AuthenticationException authException) throws IOException, ServletException {
7
		super.commence(request, response, authException);
8
	}
9
10
	@Override
11
	public void afterPropertiesSet() throws Exception {
12
		// TODO Auto-generated method stub
13
		this.setRealmName("TEST_REALM");
14
		super.afterPropertiesSet();
15
	}
16
17
}

认证服务

新建 AppSecurityConfigurer.java

1
@Configuration
2
@EnableWebSecurity
3
@EnableGlobalMethodSecurity(prePostEnabled = true)
4
class AppSecurityConfigurer extends WebSecurityConfigurerAdapter {
5
6
	private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(new AntPathRequestMatcher("/public/**"),
7
			new AntPathRequestMatcher("/user/login"));
8
9
	private static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS);
10
11
	AppUserDetailsAuthenticationProvider provider;
12
13
	AppSecurityConfigurer(final AppUserDetailsAuthenticationProvider provider) {
14
		super();
15
		this.provider = requireNonNull(provider);
16
	}
17
18
	/**
19
	 * 配置 认证服务
20
	 */
21
	@Override
22
	protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
23
		auth.authenticationProvider(provider);
24
	}
25
26
	@Override
27
	public void configure(final WebSecurity web) {
28
		web.ignoring().requestMatchers(PUBLIC_URLS);
29
	}
30
31
	/**
32
	 * 配置 spring security ,url 权限分配
33
	 * 跨域设置 .cors()
34
	 *
35
	 */
36
	@Override
37
	protected void configure(final HttpSecurity http) throws Exception {
38
		String[] putAntPatternsByAdmin = { "/products/{id}" };
39
		String[] deleteAntPatternsByAdmin = { "/products/**" };
40
		http.sessionManagement().sessionCreationPolicy(STATELESS).and().exceptionHandling()
41
				// this entry point handles when you request a protected page and you are not
42
				// yet
43
				// authenticated
44
				.defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), PROTECTED_URLS).and()
45
46
				// role config
47
				.authorizeRequests().antMatchers(HttpMethod.PUT, putAntPatternsByAdmin).hasRole("ADMIN").and()
48
				.authorizeRequests().antMatchers(HttpMethod.DELETE, deleteAntPatternsByAdmin).hasRole("ADMIN").and()
49
				// provider
50
				.authenticationProvider(provider)
51
				.addFilterBefore(restAuthenticationFilter(), AnonymousAuthenticationFilter.class).authorizeRequests()
52
53
				// cors
54
				.requestMatchers(PROTECTED_URLS).authenticated().and().cors().and().csrf().disable().formLogin()
55
				.disable()
56
				// http 认证
57
				.httpBasic().realmName("TEST_REALM").authenticationEntryPoint(getBasicAuthEntryPoint())
58
				// ...
59
				.and().logout().disable();
60
	}
61
62
	@Bean
63
	AppAuthenticationProcessingFilter restAuthenticationFilter() throws Exception {
64
		final AppAuthenticationProcessingFilter filter = new AppAuthenticationProcessingFilter(PROTECTED_URLS);
65
		filter.setAuthenticationManager(authenticationManager());
66
		filter.setAuthenticationSuccessHandler(successHandler());
67
		return filter;
68
	}
69
70
	@Bean
71
	SimpleUrlAuthenticationSuccessHandler successHandler() {
72
		final SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
73
		successHandler.setRedirectStrategy(new NoRedirectStrategy());
74
		return successHandler;
75
	}
76
77
	/**
78
	 * Disable Spring boot automatic filter registration.
79
	 */
80
	@Bean
81
	FilterRegistrationBean disableAutoRegistration(final AppAuthenticationProcessingFilter filter) {
82
		final FilterRegistrationBean registration = new FilterRegistrationBean(filter);
83
		registration.setEnabled(false);
84
		return registration;
85
	}
86
87
	@Bean
88
	AuthenticationEntryPoint forbiddenEntryPoint() {
89
		return new HttpStatusEntryPoint(FORBIDDEN);
90
	}
91
92
	/**
93
	 * ref:
94
	 * https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#cors
95
	 * spring security @CrossOrigin setting
96
	 *
97
	 * @return
98
	 */
99
	@Bean
100
	CorsConfigurationSource corsConfigurationSource() {
101
		CorsConfiguration configuration = new CorsConfiguration();
102
		configuration.setAllowedOrigins(Arrays.asList("*"));
103
		configuration.setAllowedMethods(Arrays.asList("*"));
104
		configuration.setAllowedHeaders(Arrays.asList("*"));
105
		configuration.setAllowCredentials(true);
106
		long maxAge = 60;
107
		configuration.setMaxAge(maxAge);
108
		configuration.setExposedHeaders(Arrays.asList("Authorization", "Content-Type"));
109
		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
110
		source.registerCorsConfiguration("/**", configuration);
111
		return source;
112
	}
113
114
	/**
115
	 * 认证
116
	 * http://websystique.com/spring-security/secure-spring-rest-api-using-basic-authentication/
117
	 *
118
	 * @return
119
	 */
120
	@Bean
121
	public AppBasicAuthenticationEntryPoint getBasicAuthEntryPoint() {
122
		return new AppBasicAuthenticationEntryPoint();
123
	}
124
}