spring security在分布式项目下的配置方法(案例详解)

分布式项目和传统项目的区别就是,分布式项目有多个服务,每一个服务仅仅只实现一套系统中一个或几个功能,所有的服务组合在一起才能实现系统的完整功能。这会产生一个问题,多个服务之间session不能共享,你在其中一个服务中登录了,登录信息保存在这个服务的session中,别的服务不知道啊,所以你访问别的服务还得在重新登录一次,对用户十分不友好。为了解决这个问题,于是就产生了单点登录:

**jwt单点登录:**就是用户在登录服务登录成功后,登录服务会产生向前端响应一个token(令牌),以后用户再访问系统的资源的时候都要带上这个令牌,各大服务对这个令牌进行验证(令牌是否过期,令牌是否被篡改),验证通过了,可以访问资源,同时,令牌中也会携带一些不重要的信息,比如用户名,权限。通过解析令牌就能知道当前登录的用户和用户所拥有的权限。

下面我们就来写一个案例项目看看具体如何使用

1 创建项目结构

1.1 父工程cloud-security

这是父工程所需要的包

<parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.1.3.RELEASE</version>
 <relativePath/>
</parent>

<dependencies>

 <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
 </dependency>

 <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
 </dependency>

 <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
 </dependency>
</dependencies>

1.2 公共工程 security-common

这是公共工程所需要的包

<dependency>
 <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
</dependency>

<dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>fastjson</artifactId>
 <version>1.2.60</version>
</dependency>

<!--jwt所需包-->
<dependency>
 <groupId>io.jsonwebtoken</groupId>
 <artifactId>jjwt-api</artifactId>
 <version>0.11.2</version>
</dependency>
<dependency>
 <groupId>io.jsonwebtoken</groupId>
 <artifactId>jjwt-impl</artifactId>
 <version>0.11.2</version>
 <scope>runtime</scope>
</dependency>
<dependency>
 <groupId>io.jsonwebtoken</groupId>
 <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
 <version>0.11.2</version>
 <scope>runtime</scope>
</dependency>

1.3 认证服务security-sever

这个服务仅仅只有两项功能:

(1)用户登录,颁发令牌

(2)用户注册

我们这里只实现第一个功能

1.3.1 认证服务所需的包

<dependency>
 <groupId>cn.lx.security</groupId>
 <artifactId>security-common</artifactId>
 <version>1.0-SNAPSHOT</version>
</dependency>

<dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
</dependency>
<!--通用mapper-->
<dependency>
 <groupId>tk.mybatis</groupId>
 <artifactId>mapper-spring-boot-starter</artifactId>
 <version>2.0.4</version>
</dependency>

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

1.3.2 配置application.yml

这里面的配置没什么好说的,都很简单

server:
 port: 8080

spring:
 datasource:
 url: jdbc:mysql:///security_authority?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
 username: root
 password:
 driver-class-name: com.mysql.cj.jdbc.Driver
 thymeleaf:
 cache: false
 main:
 allow-bean-definition-overriding: true

mybatis:
 type-aliases-package: cn.lx.security.doamin
 configuration:
 #驼峰
 map-underscore-to-camel-case: true

logging:
 level:
 cn.lx.security: debug

1.3.3 导入domain,dao,service,config

这个可以在上篇文档中找到,我们只需要service中的loadUserByUsername方法及其所调用dao中的方法

完整项目在我的github中,地址:git@github.com:lx972/cloud-security.git

配置文件我们也从上篇中复制过来MvcConfig,SecurityConfig

1.3.4 测试

访问http://localhost:8080/loginPage成功出现登录页面,说明认证服务的骨架搭建成功了

1.4 资源服务security-resource1

实际项目中会有很多资源服务,我只演示一个

为了简单,资源服务不使用数据库

1.4.1 资源服务所需的包

<dependency>
 <groupId>cn.lx.security</groupId>
 <artifactId>security-common</artifactId>
 <version>1.0-SNAPSHOT</version>
</dependency>

1.4.2 配置application.yml

server:
 port: 8090

logging:
 level:
 cn.lx.security: debug

1.4.3 controller

拥有ORDER_LIST权限的才能访问

@RestController
@RequestMapping("/order")
public class OrderController {

 //@Secured("ORDER_LIST")
 @PreAuthorize(value = "hasAuthority('ORDER_LIST')")
 @RequestMapping("/findAll")
 public String findAll(){
  return "order-list";
 }
}

拥有PRODUCT_LIST权限的才能访问

@RestController
@RequestMapping("/product")
public class ProductController {

 //@Secured("PRODUCT_LIST")
 @PreAuthorize(value = "hasAuthority('PRODUCT_LIST')")
 @RequestMapping("/findAll")
 public String findAll(){
  return "product-list";
 }
}

1.4.4 security配置类

@Configuration
@EnableWebSecurity
//这个注解先不要加
//@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

 /**
  * Override this method to configure the {@link HttpSecurity}. Typically subclasses
  * should not invoke this method by calling super as it may override their
  * configuration. The default configuration is:
  *
  * <pre>
  * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
  * </pre>
  *
  * @param http the {@link HttpSecurity} to modify
  * @throws Exception if an error occurs
  */
 @Override
 protected void configure(HttpSecurity http) throws Exception {
  http
    .csrf().disable()
    .authorizeRequests().anyRequest().authenticated();
 }
}

1.4.5 测试

访问http://localhost:8090/order/findAll成功打印出order-list,服务搭建成功。

2 认证服务实现登录,颁发令牌

首先,我们必须知道我们的项目是前后端分离的项目,所以我们不能由后端控制页面跳转了,只能返回json串通知前端登录成功,然后前端根据后端返回的信息控制页面跳转。

2.1 登录成功或者登录失败后的源码分析

UsernamePasswordAuthenticationFilter中登录成功后走successfulAuthentication方法

/**
	 * Default behaviour for successful authentication.认证成功之后的默认操作
	 * <ol>
	 * <li>Sets the successful <tt>Authentication</tt> object on the
	 * {@link SecurityContextHolder}</li>
	 * <li>Informs the configured <tt>RememberMeServices</tt> of the successful login</li>
	 * <li>Fires an {@link InteractiveAuthenticationSuccessEvent} via the configured
	 * <tt>ApplicationEventPublisher</tt></li>
	 * <li>Delegates additional behaviour to the {@link AuthenticationSuccessHandler}.</li>
	 * </ol>
	 *
	 * Subclasses can override this method to continue the {@link FilterChain} after
	 * successful authentication.
	 * @param request
	 * @param response
	 * @param chain
	 * @param authResult the object returned from the <tt>attemptAuthentication</tt>
	 * method.
	 * @throws IOException
	 * @throws ServletException
	 */
	protected void successfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, Authentication authResult)
			throws IOException, ServletException {

		if (logger.isDebugEnabled()) {
			logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
					+ authResult);
		}

  //将已通过认证的Authentication保存到securityContext容器中,应为后面的过滤器需要使用
		SecurityContextHolder.getContext().setAuthentication(authResult);

  //记住我
		rememberMeServices.loginSuccess(request, response, authResult);

		// Fire event
		if (this.eventPublisher != null) {
			eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
					authResult, this.getClass()));
		}

  //这个方法你点进去,就会发现,真正作业面跳转是在这里
		successHandler.onAuthenticationSuccess(request, response, authResult);
	}

UsernamePasswordAuthenticationFilter中登录成功后走unsuccessfulAuthentication方法

/**
	 * Default behaviour for unsuccessful authentication.认证失败之后的默认操作
	 * <ol>
	 * <li>Clears the {@link SecurityContextHolder}</li>
	 * <li>Stores the exception in the session (if it exists or
	 * <tt>allowSesssionCreation</tt> is set to <tt>true</tt>)</li>
	 * <li>Informs the configured <tt>RememberMeServices</tt> of the failed login</li>
	 * <li>Delegates additional behaviour to the {@link AuthenticationFailureHandler}.</li>
	 * </ol>
	 */
	protected void unsuccessfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException failed)
			throws IOException, ServletException {
		SecurityContextHolder.clearContext();

		if (logger.isDebugEnabled()) {
			logger.debug("Authentication request failed: " + failed.toString(), failed);
			logger.debug("Updated SecurityContextHolder to contain null Authentication");
			logger.debug("Delegating to authentication failure handler " + failureHandler);
		}

  //记住我失败
		rememberMeServices.loginFail(request, response);

  //失败后的页面跳转都在这里
		failureHandler.onAuthenticationFailure(request, response, failed);
	}

2.2 重写successfulAuthentication和unsuccessfulAuthentication方法

我们继承UsernamePasswordAuthenticationFilter这个过滤器

public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {

  /**
  * 这个方法必须有
  * 在过滤器创建的时候手动将AuthenticationManager对象给这个过滤器使用
  * @param authenticationManager 这个对象在自己写的SecurityConfig里面
  */
 public AuthenticationFilter(AuthenticationManager authenticationManager) {
  super.setAuthenticationManager(authenticationManager);
 }

 /**
  * Default behaviour for successful authentication.认证成功之后的默认操作
  * @param request
  * @param response
  * @param chain
  * @param authResult the object returned from the <tt>attemptAuthentication</tt>
  *     method.
  * @throws IOException
  * @throws ServletException
  */
 @Override
 protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {

  //认证成功的对象放入securityContext容器中
  SecurityContextHolder.getContext().setAuthentication(authResult);

  // Fire event
  if (this.eventPublisher != null) {
   eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
     authResult, this.getClass()));
  }
  //创建令牌
  Map<String, Object> claims=new HashMap<>();
  SysUser sysUser = (SysUser) authResult.getPrincipal();
  claims.put("username",sysUser.getUsername());
  claims.put("authorities",authResult.getAuthorities());
  //这个方法在下面介绍
  String jwt = JwtUtil.createJwt(claims);
  //直接返回json
  ResponseUtil.responseJson(new Result("200", "登录成功",jwt),response);
 }

 /**
  * Default behaviour for unsuccessful authentication.
  * @param request
  * @param response
  * @param failed
  */
 @Override
 protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
   //清理容器中保存的认证对象
  SecurityContextHolder.clearContext();
  //直接返回json
  ResponseUtil.responseJson(new Result("500", "登录失败"),response);

 }
}

2.3 令牌创建

String jwt = JwtUtil.createJwt(claims);

这个方法干了什么事呢

/**
  * 创建令牌
  * @param claims
  * @return
  */
public static String createJwt(Map<String, Object> claims){
 //获取私钥
 String priKey = KeyUtil.readKey("privateKey.txt");
 //将string类型的私钥转换成PrivateKey,jwt只能接受PrivateKey的私钥
 PKCS8EncodedKeySpec priPKCS8 = null;
 try {
  priPKCS8 = new PKCS8EncodedKeySpec(new BASE64Decoder().decodeBuffer(priKey));
  KeyFactory keyf = KeyFactory.getInstance("RSA");
  PrivateKey privateKey = keyf.generatePrivate(priPKCS8);
  //创建令牌
  String jws = Jwts.builder()
   //设置令牌过期时间30分钟
   .setExpiration(new Date(System.currentTimeMillis()+1000*60*30))
   //为令牌设置额外的信息,这里我们设置用户名和权限,还可以根据需要继续添加
   .addClaims(claims)
   //指定加密类型为rsa
   .signWith(privateKey, SignatureAlgorithm.RS256)
   //得到令牌
   .compact();
  log.info("创建令牌成功:"+jws);
  return jws;
 } catch (Exception e) {
  throw new RuntimeException("创建令牌失败");
 }
}

获取秘钥的方法

public class KeyUtil {

 /**
  * 读取秘钥
  * @param keyName
  * @return
  */
 public static String readKey(String keyName){
  //文件必须放在resources根目录下
  ClassPathResource resource=new ClassPathResource(keyName);
  String key =null;
  try {
   InputStream is = resource.getInputStream();
   key = StreamUtils.copyToString(is, Charset.defaultCharset());
  }catch (Exception e){
   throw new RuntimeException("读取秘钥错误");
  }
  if (key==null){
   throw new RuntimeException("秘钥为空");
  }
  return key;
 }
}

2.4 响应json格式数据给前端

封装成了一个工具类

public class ResponseUtil {

 /**
  * 将结果以json格式返回
  * @param result 返回结果
  * @param response
  * @throws IOException
  */
 public static void responseJson(Result result, HttpServletResponse response) throws IOException {
  response.setContentType("application/json;charset=utf-8");
  response.setStatus(200);
  PrintWriter writer = response.getWriter();
  writer.write(JSON.toJSONString(result));
  writer.flush();
  writer.close();
 }
}

返回结果

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {

 private String code;
 private String msg;
 private Object data;

 public Result(String code, String msg) {
  this.code = code;
  this.msg = msg;
 }
}

3 认证服务实现令牌验证和解析

除了security配置类中配置的需要忽略的请求之外,其他所有请求必须验证请求头中是否携带令牌,没有令牌直接响应json数据,否则就验证和解析令牌。

security中有一个过滤器是实现令牌BasicAuthenticationFilter认证的,只不过他是basic的,没关系,我们继承它,然后重写解析basic的方法

3.1 源码分析

@Override
protected void doFilterInternal(HttpServletRequest request,
        HttpServletResponse response, FilterChain chain)
 throws IOException, ServletException {
 final boolean debug = this.logger.isDebugEnabled();

 //获取请求头中Authorization的值
 String header = request.getHeader("Authorization");

 if (header == null || !header.toLowerCase().startsWith("basic ")) {
  //值不符合条件直接放行
  chain.doFilter(request, response);
  return;
 }

 try {
  //就是解析Authorization
  String[] tokens = extractAndDecodeHeader(header, request);
  assert tokens.length == 2;

  //tokens[0]用户名 tokens[1]密码
  String username = tokens[0];

  if (debug) {
   this.logger
    .debug("Basic Authentication Authorization header found for user '"
      + username + "'");
  }

  //判断是否需要认证(容器中有没有该认证对象)
  if (authenticationIsRequired(username)) {
   //创建一个对象
   UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
    username, tokens[1]);
   authRequest.setDetails(
    this.authenticationDetailsSource.buildDetails(request));
   //进行认证,我们不关心它如何认证,我们需要按自己的方法对令牌认证解析
   Authentication authResult = this.authenticationManager
    .authenticate(authRequest);

   if (debug) {
    this.logger.debug("Authentication success: " + authResult);
   }

   //已认证的对象保存到securityContext中
   SecurityContextHolder.getContext().setAuthentication(authResult);

   //记住我
   this.rememberMeServices.loginSuccess(request, response, authResult);

   onSuccessfulAuthentication(request, response, authResult);
  }

 }
 catch (AuthenticationException failed) {
  SecurityContextHolder.clearContext();

  if (debug) {
   this.logger.debug("Authentication request for failed: " + failed);
  }

  this.rememberMeServices.loginFail(request, response);

  onUnsuccessfulAuthentication(request, response, failed);

  if (this.ignoreFailure) {
   chain.doFilter(request, response);
  }
  else {
   this.authenticationEntryPoint.commence(request, response, failed);
  }

  return;
 }

 chain.doFilter(request, response);
}

3.2 重写doFilterInternal方法

继承BasicAuthenticationFilter

public class TokenVerifyFilter extends BasicAuthenticationFilter {

 /**
  * Creates an instance which will authenticate against the supplied
  * {@code AuthenticationManager} and which will ignore failed authentication attempts,
  * allowing the request to proceed down the filter chain.
  * 在过滤器创建的时候手动将AuthenticationManager对象给这个过滤器使用
  * @param authenticationManager 这个对象在自己写的SecurityConfig里面
  */
 public TokenVerifyFilter(AuthenticationManager authenticationManager) {
  super(authenticationManager);
 }

 /**
  * 过滤请求,判断是否携带令牌
  * @param request
  * @param response
  * @param chain
  * @throws IOException
  * @throws ServletException
  */
 @Override
 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {

  String header = request.getHeader("Authorization");

  if (header == null || !header.toLowerCase().startsWith("bearer ")) {
   //直接返回json
   ResponseUtil.responseJson(new Result("403", "用户未登录"),response);
   return;
  }

  //得到jwt令牌
  String jwt = StringUtils.replace(header, "bearer ", "");
  //解析令牌
  String[] tokens = JwtUtil.extractAndDecodeJwt(jwt);

  //用户名
  String username = tokens[0];
  //权限
  List<SysPermission> authorities= JSON.parseArray(tokens[1], SysPermission.class);

  UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
    username,
    null,
    authorities
    );

  //放入SecurityContext容器中
  SecurityContextHolder.getContext().setAuthentication(authRequest);

  chain.doFilter(request, response);
 }
}

3.3 验证解析令牌

/**
  * 解析令牌
  * @param compactJws
  * @return
  */
public static String decodeJwt(String compactJws){
 //获取公钥
 String pubKey = KeyUtil.readKey("publicKey.txt");
 //将string类型的私钥转换成PublicKey,jwt只能接受PublicKey的公钥
 KeyFactory keyFactory;
 try {
  X509EncodedKeySpec bobPubKeySpec = new X509EncodedKeySpec(
   new BASE64Decoder().decodeBuffer(pubKey));

  keyFactory = KeyFactory.getInstance("RSA");
  PublicKey publicKey = keyFactory.generatePublic(bobPubKeySpec);

  Claims body = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(compactJws).getBody();

  String jwtString = JSON.toJSONString(body);

  //OK, we can trust this JWT
  log.info("解析令牌成功:"+jwtString);
  return jwtString;
 } catch (Exception e) {
  throw new RuntimeException("解析令牌失败");
 }
}

/**
  * 解析令牌并获取用户名和权限
  * @param compactJws
  * @return String[0]用户名
  * String[1]权限
  */
public static String[] extractAndDecodeJwt(String compactJws){
 //获取令牌的内容
 String decodeJwt = decodeJwt(compactJws);
 JSONObject jsonObject = JSON.parseObject(decodeJwt);
 String username = jsonObject.getString("username");
 String authorities = jsonObject.getString("authorities");
 return new String[] { username, authorities };
}

3.4 修改security配置类

将自定义过滤器加入过滤器链

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

 @Autowired
 private IUserService iUserService;

 @Autowired
 private BCryptPasswordEncoder bCryptPasswordEncoder;

 /**
  * 只有这个配置类有AuthenticationManager对象,我们要把这个类中的这个对象放入容器中
  * 这样在别的地方就可以自动注入了
  * @return
  * @throws Exception
  */
 @Bean
 @Override
 public AuthenticationManager authenticationManager() throws Exception {
  AuthenticationManager authenticationManager = super.authenticationManagerBean();
  return authenticationManager;
 }

 /**
  * Used by the default implementation of {@link #authenticationManager()} to attempt
  * to obtain an {@link AuthenticationManager}. If overridden, the
  * {@link AuthenticationManagerBuilder} should be used to specify the
  * {@link AuthenticationManager}.
  *
  * <p>
  * The {@link #authenticationManagerBean()} method can be used to expose the resulting
  * {@link AuthenticationManager} as a Bean. The {@link #userDetailsServiceBean()} can
  * be used to expose the last populated {@link UserDetailsService} that is created
  * with the {@link AuthenticationManagerBuilder} as a Bean. The
  * {@link UserDetailsService} will also automatically be populated on
  * {@link HttpSecurity#getSharedObject(Class)} for use with other
  * {@link SecurityContextConfigurer} (i.e. RememberMeConfigurer )
  * </p>
  *
  * <p>
  * For example, the following configuration could be used to register in memory
  * authentication that exposes an in memory {@link UserDetailsService}:
  * </p>
  *
  * <pre>
  * @Override
  * protected void configure(AuthenticationManagerBuilder auth) {
  * 	auth
  * 	// enable in memory based authentication with a user named
  * 	// &quot;user&quot; and &quot;admin&quot;
  * 	.inMemoryAuthentication().withUser(&quot;user&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;).and()
  * 			.withUser(&quot;admin&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;, &quot;ADMIN&quot;);
  * }
  *
  * // Expose the UserDetailsService as a Bean
  * @Bean
  * @Override
  * public UserDetailsService userDetailsServiceBean() throws Exception {
  * 	return super.userDetailsServiceBean();
  * }
  *
  * </pre>
  *
  * @param auth the {@link AuthenticationManagerBuilder} to use
  * @throws Exception
  */
 @Override
 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  //在内存中注册一个账号
  //auth.inMemoryAuthentication().withUser("user").password("{noop}123").roles("USER");
  //连接数据库,使用数据库中的账号
  auth.userDetailsService(iUserService).passwordEncoder(bCryptPasswordEncoder);

 }

 /**
  * Override this method to configure {@link WebSecurity}. For example, if you wish to
  * ignore certain requests.
  *
  * @param web
  */
 @Override
 public void configure(WebSecurity web) throws Exception {
  web.ignoring().antMatchers("/css/**",
    "/img/**",
    "/plugins/**",
    "/favicon.ico",
    "/loginPage");
 }

 /**
  * Override this method to configure the {@link HttpSecurity}. Typically subclasses
  * should not invoke this method by calling super as it may override their
  * configuration. The default configuration is:
  *
  * <pre>
  * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
  * </pre>
  *
  * @param http the {@link HttpSecurity} to modify
  * @throws Exception if an error occurs
  */
 @Override
 protected void configure(HttpSecurity http) throws Exception {
  http.csrf().disable()
    .httpBasic()
    .and()
    .authorizeRequests()
    .anyRequest().authenticated()
    .and()
    /**
     * 不要将自定义过滤器加component注解,而是在这里直接创建一个过滤器对象加入到过滤器链中,并传入authenticationManager
     * 启动后,过滤器链中会同时出现自定义过滤器和他的父类,他会自动覆盖,并不会过滤两次
     *
     * 使用component注解会产生很多问题:
     * 1. web.ignoring()会失效,上面的资源还是会经过自定义的过滤器
     * 2.过滤器链中出现的是他们父类中的名字
     * 3.登录的时候(访问/login),一直使用匿名访问,不会去数据库中查询
     */
    .addFilterAt(new AuthenticationFilter(super.authenticationManager()), UsernamePasswordAuthenticationFilter.class)
    .addFilterAt(new TokenVerifyFilter(super.authenticationManager()), BasicAuthenticationFilter.class)
    //.formLogin().loginPage("/login.jsp").loginProcessingUrl("/login").defaultSuccessUrl("/index.jsp").failureForwardUrl("/failer.jsp").permitAll()
    .formLogin().loginPage("/loginPage").loginProcessingUrl("/login").permitAll()
    .and()
    .logout().logoutUrl("/logout").logoutSuccessUrl("/loginPage").invalidateHttpSession(true).permitAll();
 }
}

4 资源服务实现令牌验证和解析

复制认证服务的TokenVerifyFilter到资源服务

然后修改security的配置文件

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 /**
  * Override this method to configure the {@link HttpSecurity}. Typically subclasses
  * should not invoke this method by calling super as it may override their
  * configuration. The default configuration is:
  *
  * <pre>
  * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
  * </pre>
  *
  * @param http the {@link HttpSecurity} to modify
  * @throws Exception if an error occurs
  */
 @Override
 protected void configure(HttpSecurity http) throws Exception {
  http
    .csrf().disable()
    .authorizeRequests().anyRequest().authenticated()
    .and()
    //禁用session
    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    .and()
   //添加自定义过滤器
    .addFilterAt(new TokenVerifyFilter(super.authenticationManager()), BasicAuthenticationFilter.class);
 }
}

到此这篇关于spring security在分布式项目下的配置方法(案例详解)的文章就介绍到这了,更多相关spring security分布式内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解spring security 配置多个AuthenticationProvider

    前言 发现很少关于spring security的文章,基本都是入门级的,配个UserServiceDetails或者配个路由控制就完事了,而且很多还是xml配置,国内通病...so,本文里的配置都是java配置,不涉及xml配置,事实上我也不会xml配置 spring security的大体介绍 spring security本身如果只是说配置,还是很简单易懂的(我也不知道网上说spring security难,难在哪里),简单不需要特别的功能,一个WebSecurityConfigurerA

  • Spring Boot Security 结合 JWT 实现无状态的分布式API接口

    简介 JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案.JSON Web Token 入门教程 这篇文章可以帮你了解JWT的概念.本文重点讲解Spring Boot 结合 jwt ,来实现前后端分离中,接口的安全调用. Spring Security,这是一种基于 Spring AOP 和 Servlet 过滤器的安全框架.它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权. 快速上手 之前的文章已经对 Spring Security 进行

  • spring security国际化及UserCache的配置和使用

    国际化配置 <!-- 定义上下文返回的消息的国际化 --> <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="classpath:config/messages_zh_CN&qu

  • SpringBoot + Spring Security 基本使用及个性化登录配置详解

    Spring Security 基本介绍 这里就不对Spring Security进行过多的介绍了,具体的可以参考官方文档 我就只说下SpringSecurity核心功能: 认证(你是谁) 授权(你能干什么) 攻击防护(防止伪造身份) 基本环境搭建 这里我们以SpringBoot作为项目的基本框架,我这里使用的是maven的方式来进行的包管理,所以这里先给出集成Spring Security的方式 <dependencies> ... <dependency> <groupI

  • 详解Spring Security 简单配置

    开发环境 maven idea jdk 1.8 tomcat 8 配置spring mvc + spring security pom.xml <properties> <spring.version>4.3.8.RELEASE</spring.version> <spring-sercurity.version>4.2.2.RELEASE</spring-sercurity.version> </properties> <de

  • spring security动态配置url权限的2种实现方法

    缘起 标准的RABC, 权限需要支持动态配置,spring security默认是在代码里约定好权限,真实的业务场景通常需要可以支持动态配置角色访问权限,即在运行时去配置url对应的访问角色. 基于spring security,如何实现这个需求呢? 最简单的方法就是自定义一个Filter去完成权限判断,但这脱离了spring security框架,如何基于spring security优雅的实现呢? spring security 授权回顾 spring security 通过FilterCh

  • spring security在分布式项目下的配置方法(案例详解)

    分布式项目和传统项目的区别就是,分布式项目有多个服务,每一个服务仅仅只实现一套系统中一个或几个功能,所有的服务组合在一起才能实现系统的完整功能.这会产生一个问题,多个服务之间session不能共享,你在其中一个服务中登录了,登录信息保存在这个服务的session中,别的服务不知道啊,所以你访问别的服务还得在重新登录一次,对用户十分不友好.为了解决这个问题,于是就产生了单点登录: **jwt单点登录:**就是用户在登录服务登录成功后,登录服务会产生向前端响应一个token(令牌),以后用户再访问系

  • Spring 3.x中三种Bean配置方式比较详解

    以前Java框架基本都采用了XML作为配置文件,但是现在Java框架又不约而同地支持基于Annotation的"零配置"来代替XML配置文件,Struts2.Hibernate.Spring都开始使用Annotation来代替XML配置文件了:而在Spring3.x提供了三种选择,分别是:基于XML的配置.基于注解的配置和基于Java类的配置. 下面分别介绍下这三种配置方式:首先定义一个用于举例的JavaBean. package com.chinalife.dao public cl

  • Spring Boot 2.0多数据源配置方法实例详解

    两个数据库实例,一个负责读,一个负责写. datasource-reader: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://192.168.43.61:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false username: icbc password: icbc driver-class-na

  • Vue多环境代理配置方法思路详解

    背景: 多人协作模式下,修改代理比较麻烦,而且很容易某个开发人员会修改了vue.config.js文件后提交了. 第一,很容易引起冲突. 第二,很容易出现代理错误,需要排查.而且现在微服务盛行,在生产环境下有网关配置,不会出错,但是在本地调试会造成麻烦,如修改了代理地址需要同步修改代理中的地址,很麻烦也很容易出错. 解决思路: 1.开发中定义常量js文件,例如constants.js.用户存放各个服务需要代理的服务名. let api = "" let loginServer = &q

  • Mysql 5.7.19 免安装版配置方法教程详解(64位)

    官方网站下载mysql-5.7.19-winx64,注意对应系统64位或者32位,这里使用的是64位. 解压放置到本地磁盘.发现文件很大,大概是1.6G左右.删除lib文件夹下的.lib文件和debug文件夹下所有文件. 在主目录下创建my.ini文件,文件内容如下:(这里是简洁版,对应本机修改basedir和datadir的目录,根据需要可以自己扩充配置) [client] port=3306 default-character-set=utf8 [mysqld] basedir=D:\Jav

  • nginx rewrite重写规则与防盗链配置方法教程详解

    导读:nginx rewrite重写规则与防盗链配置方法,rewrite规则格式中flag标记的几种形式,盗链时返回403错误,允许的域名直接跟在第二行的域名后面. nginx rewrite重写规则与防盗链配置方法如下所示: nginx rewite 规则,官方文档:http://wiki.nginx.org/NginxHttpRewriteModule nginx rewrite规则格式:rewrite regex replacement flag flag标记有四种格式: last – 相

  • Linux上搭载Nginx负载均衡配置使用案例详解

    目录 1,这里我们来说下很重要的负载均衡, 那么什么是负载均衡呢? 2, 负载均衡的种类 3, 这里我们只来说Nginx(其他的大家有兴趣可以自行查阅相关文档) 4, 创建两台Nginx服务器 5, 搭建Nginx 搭建Keepalived:(Keepalived需要依赖openssl) 1,这里我们来说下很重要的负载均衡, 那么什么是负载均衡呢? 由于目前现有网络的各个核心部分随着业务量的提高,访问量和数据流量的快速增长,其处理能力和计算强度也相应地增大,使得单一的服务器设备根本无法承担.在此

  • VSCode各语言运行环境配置方法示例详解

    系统环境变量的配置 如:将F:\mingw64\bin添加到系统环境变量Path中 VSCode软件语言json配置C语言 创建个.vscode文件夹,文件夹内创建以下两个文件 launch.json 文件配置 { "version": "0.2.0", "configurations": [ { "name": "(gdb) Launch", "type": "cppdbg&

  • 小程序微信支付功能配置方法示例详解【基于thinkPHP】

    本文实例讲述了小程序微信支付功能配置方法.分享给大家供大家参考,具体如下: ★ 背景 近期进行小程序的开发,毕竟是商城项目的开发,最后牵扯到的微信支付是必要的 个人开发过程中也是遇到各种问题,在此,我根据自己的实际操作,进行了代码的详细配置,以方便小程序新手的快速操作 -  使用语言:PHP             # PHP世界上最好的语言 HaHahahaaha -  使用框架:ThinkPHP 3.2    # 版本有点低而已,没啥大碍 -  测试工具:微信开发者工具    # 其实还挺好

  • Spring MVC 框架搭建配置方法及详解

    现在主流的Web MVC框架除了Struts这个主力 外,其次就是Spring MVC了,因此这也是作为一名程序员需要掌握的主流框架,框架选择多了,应对多变的需求和业务时,可实行的方案自然就多了.不过要想灵活运用Spring MVC来应对大多数的Web开发,就必须要掌握它的配置及原理. 一.Spring MVC环境搭建:(Spring 2.5.6 + Hibernate 3.2.0) 1. jar包引入 Spring 2.5.6:spring.jar.spring-webmvc.jar.comm

随机推荐