Spring Security
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--thymeleaf对security的支持-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>${thymeleaf-extras-springsecurity5-version}</version>
</dependency>
Web端:
WebSecurityConfig
主要端配置类:WebSecurityConfig
,在这个文件中可以配置以下
-
loginPage 登录页面的地址(如果没有提供,Spring Security提供了默认的登录界面)
-
handler:
- successHandler
- authenticationFailureUrl
- logoutSuccessHandler
- session处理相关配置
- 授权管理
- 配置新的拦截器
WebSecurityConfig.java
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled=true, prePostEnabled = true)// 控制权限注解 配合 @Secured({"ROLE_ADMIN","ROLE_USER2"})使用
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private ValidateFilter validateFilter;
@Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler authenticationFailureHandler;
@Autowired
private LogoutSuccessHandler logoutSuccessHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().disable()
.authorizeRequests()
.antMatchers("/forbidden", "/kaptcha").permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
.permitAll()
.and()
.logout()
.logoutSuccessHandler(logoutSuccessHandler)
.permitAll()
.and()
.csrf().disable()// csrf会拦截POST请求 https://www.jianshu.com/p/2c275c75c77a
.sessionManagement().
invalidSessionUrl("/login")
.maximumSessions(1)
.expiredSessionStrategy(new AcExpiredSessionStrategy())
.sessionRegistry(sessionRegistry());
http.addFilterBefore(validateFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
public void configure(WebSecurity web) throws Exception {
//解决静态资源被拦截的问题
web.ignoring().antMatchers("/coreui/**", "/css/**", "/js/**", "/img/**", "/plugins/**", "/favicon.ico");
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
}
PasswordEncoder
密码加密处理
pom.xml
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
用户认证服务UserDetailsService
@Component
@Slf4j
public class AcUserDetailsService implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private CacheManager cacheManager;
@Autowired
private SessionRegistry sessionRegistry;
/**
* 进行认证授权的工作
*
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) {
username = username.toLowerCase();
Cache tryCache = cacheManager.getCache("loginMaxTry");
Object object = tryCache.get(username);
int loginMaxTryCount = 1;
if (Objects.isNull(object)) {
tryCache.put(username, loginMaxTryCount);
} else {
loginMaxTryCount = (Integer) object + 1;
tryCache.put(username, loginMaxTryCount);
}
if (loginMaxTryCount > AuthConstants.MAX_TRY_COUNT) {
throw new MaxTryLoginException();
}
UserDTO user;
try {
user = userService.findByUsername(username);
} catch (DataAccessException e) {
throw new DataAccessResourceFailureException("数据库连接出错, 请联系管理员");
}
if (user == null) {
throw new UsernameNotFoundException("帐号不存在或者账号被锁定");
}
if (isLogin(username)) {
log.info("LOGIN: {}已经登录,踢出登录", username );
}
return new AcUserDetails(user, AuthorityUtils.createAuthorityList(user.getRoles().toArray(new String[]{})));
}
/**
* 用户是否处于登录状态
* @param username
* @return
*/
private boolean isLogin(String username) {
List<Object> list = sessionRegistry.getAllPrincipals();
for (Object o : list) {
if (StringUtils.equals(((AcUserDetails)o).getUsername(), username)) {
return sessionRegistry.getAllSessions(o, false).size() > 0;
}
}
return false;
}
}
Spring Security OAuth2
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
在Spring Security的基础上多次两个概念
* @EnableAuthorizationServer 授权服务器
* @EnableResourceServer 资源服务器
如果授权服务器和资源服务器部署「在」同一台服务器上
-
采用授权码模式会有问题,因为采用授权码模式需要跳转「登录」界面,需要使用
WebSecurityConfig
。WebSecurityConfig
和@EnableResourceServer
会有冲突。 -
采用密码模式没有问题。不需要依赖
WebSecurityConfig
,因为不依赖WebSecurityConfig
,需要自己构造AuthenticationManager
,测试时否则会报异常。
@Bean
public AuthenticationManager authenticationManager(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
List<AuthenticationProvider> providers = new ArrayList<>();
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
providers.add(daoAuthenticationProvider);
return new ProviderManager(providers);
}
如果授权服务器和资源服务器部署「不在」同一台服务器上
-
授权服务器
WebSecurityConfig
+@EnableAuthorizationServer
授权码和密码模式@EnableAuthorizationServer
密码模式
-
资源服务器
@EnableResourceServer
授权服务器
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
@Qualifier("tokenServices")
private AuthorizationServerTokenServices tokenServices;
@Autowired
private AuthorizationCodeServices authorizationCodeServices;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()") //oauth/token_key是公开
.checkTokenAccess("permitAll()")//oauth/check_token公开
.allowFormAuthenticationForClients() //密码模式:表单认证(申请令牌)
;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()// 使用in-memory存储
.withClient("c1")// client_id
.secret(passwordEncoder.encode("secret"))//客户端密钥
.resourceIds("res1")//资源列表
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")// 该client允许的授权类型authorization_code,password,refresh_token,implicit,client_credentials
.scopes("all")// 允许的授权范围
.autoApprove(false)//false跳转到授权页面
//加上验证回调地址
.redirectUris("http://www.baidu.com");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.userDetailsService(userDetailsService)
.authenticationManager(authenticationManager)// 认证管理器 => 密码模式需要在认证服务器中设置 中配置AuthenticationManager
.authorizationCodeServices(authorizationCodeServices)//授权码服务
.tokenServices(tokenServices)// 令牌管理服务
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
资源服务器
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
public static final String RESOURCE_ID = "res1";
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
// 资源id
resources.resourceId(RESOURCE_ID)
.tokenStore(tokenStore)
.stateless(true)
.accessDeniedHandler((request, response, e) -> {
response.getWriter().write("look=>" + e);
});
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/order/r2").hasAnyAuthority("p2")
.antMatchers("/login*").permitAll()
.anyRequest().authenticated()
// 需要身份认证的时候跳转的URL(可以直接指定html)
;
}
}