1. 程式人生 > >spring boot 2.X整合spring security和spring oauth

spring boot 2.X整合spring security和spring oauth

​ 網上有很多該系列的教程,但是很多都是spring boot1.x,很少看見關於spring boot 2.0 .本人是打算做個spring cloud的web程式,這個整合我就是放在zuul上,類似於做了個閘道器的鑑權吧。。

國際通用案例 上jar包依賴

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.4.RELEASE</
version
>
<relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version
>
1.8</java.version> <spring.druid.version>1.1.10</spring.druid.version> <spring.mybatis.version>1.3.2</spring.mybatis.version> <mapper.spring.version>2.0.4</mapper.spring.version> <pagehelper.spring.version>1.2.6</pagehelper.spring.version
>
<jwt.verson>0.9.0</jwt.verson> </properties> <!--安全驗證相關--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> </dependency> <!--解析JWT--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>${jwt.verson}</version> </dependency>

我們首先要寫登入邏輯

@Component
@Slf4j
public class AppUserDetailsService implements UserDetailsService {
	@Autowired
	private PasswordEncoder passwordEncoder;
	/**
	 * 表單登入
	 */
	@Override
	public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
		return buildUser(userName);
	}
	//使用者必須要有ROLE_USER 才可以登入 服務提供商
	private UserDetails buildUser(String userId) {
		// 根據使用者名稱查詢使用者資訊,這裡可以寫我們的登入邏輯比如說XXXXservice.findUser(String user);
		//根據查詢到的使用者資訊判斷使用者是否被凍結
		String password = passwordEncoder.encode("123456");
		log.info("資料庫密碼是:"+password);
		return new User(userId, password,
				true, true, true, true,
				AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN,ROLE_USER"));
	}
}

配置我們的資源伺服器@EnableResourceServer

@Configuration
@EnableResourceServer
public class AppWebSecurityConfigurerAdapter extends ResourceServerConfigurerAdapter {

   private final AppSecurityExpressionHandler appSecurityExpressionHandler;
   private final ZuulProperties zuulProperties;
   private final AuthenticationSuccessHandler appAuthenticationSuccessHandler;
   private final AuthenticationFailureHandler appAuthenticationFailureHandler;
   private final AccessDeniedHandler appAccessDeniedHandler;

   public AppWebSecurityConfigurerAdapter(AppSecurityExpressionHandler appSecurityExpressionHandler, ZuulProperties zuulProperties, AuthenticationSuccessHandler appAuthenticationSuccessHandler, AuthenticationFailureHandler appAuthenticationFailureHandler, AccessDeniedHandler appAccessDeniedHandler) {
      this.appSecurityExpressionHandler = appSecurityExpressionHandler;
      this.zuulProperties = zuulProperties;
      this.appAuthenticationSuccessHandler = appAuthenticationSuccessHandler;
      this.appAuthenticationFailureHandler = appAuthenticationFailureHandler;
      this.appAccessDeniedHandler = appAccessDeniedHandler;
   }



   @Override
   public void configure(HttpSecurity http) throws Exception {
      http
         .formLogin()
            //登入失敗過濾器
            .failureHandler(appAuthenticationFailureHandler)
          	//配置登入成功過濾器
            .successHandler(appAuthenticationSuccessHandler)
          	//配置登入地址
            .loginPage(ZuulAppConstant.LOGIN_URL)
          	//配置未登入跳轉URL
            .loginProcessingUrl(ZuulAppConstant.LOGIN_JUMP_CONTROLLER)
          //配置get不需要驗證的URL
         .and()
            .authorizeRequests()
            .antMatchers(HttpMethod.GET,zuulProperties.getAuth().toGetAdapter())
            .permitAll()
          //配置post不需要驗證的URL
         .and()
            .authorizeRequests()
            .antMatchers(HttpMethod.POST,zuulProperties.getAuth().toPostAdapter())
            .permitAll()
          //除上述URL,都需要登入使用者
         .and()
            .authorizeRequests()
            .anyRequest()
            .authenticated()
          //配置使用者無許可權登入過濾器
         .and()
            .exceptionHandling().accessDeniedHandler(appAccessDeniedHandler)
         .and()
            .csrf().disable()
            .authorizeRequests()
            .anyRequest()
           //配置授權驗證服務
            .access("@defaultZuulAuthorizationService.hasPermission(request,authentication)");
   }
    //和鑑權服務有關,springboot2.0新加入部分
    @Override
	public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
		// TODO Auto-generated method stub
		resources.expressionHandler(appSecurityExpressionHandler);
	}
}

spring security配置,與spring oauth2有關

@EnableWebSecurity
@Order(6)
public class AppSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    //spring boot 新加的
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

過濾器配置

配置登陸成功過濾器

/**
 * 認證成功跳轉
 * @author w4837
 *
 */
@Component(value = "AppAuthenticationSuccessHandler")
@Slf4j
public class AppAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

	@Autowired
	private ObjectMapper objectMapper;
	@Autowired
	private ClientDetailsService clientDetailsService;

	@Override
	@Order
	public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
					HttpServletResponse httpServletResponse, Authentication authentication)
			throws IOException, ServletException {
		log.info("登陸成功");
	}
}

###配置登入失敗過濾器

@Slf4j
@Component("AppAuthenticationFailureHandler")
public class AppAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler{

	@Autowired
	private ObjectMapper objectMapper;

	@Override
	public void onAuthenticationFailure(HttpServletRequest request
			, HttpServletResponse response, AuthenticationException exception)
			throws IOException, ServletException {
		log.info("登入失敗");
		super.onAuthenticationFailure(request, response, exception);
	}

}

配置無許可權服務過濾器

@Component("AppAccessDeniedHandler")
public class AppAccessDeniedHandler implements AccessDeniedHandler{

	@Autowired
	private ObjectMapper objectMapper;
	
	@Override
	public void handle(HttpServletRequest request, HttpServletResponse response,
			AccessDeniedException accessDeniedException) throws IOException, ServletException {
	   response.setContentType(ZuulAppConstant.CONTENT_TYPE_JSON);
       response.setStatus(HttpStatus.SC_FORBIDDEN);
       response.getWriter().write(objectMapper.writeValueAsString(ResultVo.createErrorResult("當前使用者訪問許可權不夠,請聯絡管理員增加對應許可權",403)));
	}

}

鑑權服務過濾器 spring boot 2.x新加

@Configuration("appSecurityExpressionHandler")
public class AppSecurityExpressionHandler extends OAuth2WebSecurityExpressionHandler{

	@Bean
    public OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler(ApplicationContext applicationContext) {
        OAuth2WebSecurityExpressionHandler expressionHandler = new OAuth2WebSecurityExpressionHandler();
        expressionHandler.setApplicationContext(applicationContext);
        return expressionHandler;
    }
}

鑑權服務

/**
 * Copyright © 2018 eSunny Info. Tech Ltd. All rights reserved.
 *
 * @author [email protected]
 * @program: app-management
 * @title:ZuulAuthorizationService
 * @Package com.yulece.app.management.zuul.authorization.service
 * @Description:
 * @Date 建立時間 2018/9/30-18:23
 **/
public interface ZuulAuthorizationService {

    /**
     * 驗證使用者是否有權登入
     * @param request
     * @param authentication
     * @return
     */
    boolean hasPermission(HttpServletRequest request , Authentication authentication);
}


/**
 * Copyright © 2018 eSunny Info. Tech Ltd. All rights reserved.
 *
 * @author [email protected]
 * @program: app-management
 * @title:DefaultRbacService
 * @Package com.yulece.app.management.zuul.authorization.service
 * @Description:
 * @Date 建立時間 2018/9/30-18:58
 **/
@Component("defaultZuulAuthorizationService")
public class DefaultZuulAuthorizationService implements ZuulAuthorizationService {

    private final static Logger LOGGER = LoggerFactory.getLogger(DefaultZuulAuthorizationService.class);

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        boolean isPermission = false;
        String requestURI = request.getRequestURI();
        if(StringUtils.isEmpty(requestURI)){
            return isPermission;
        }
        //匿名使用者不能經過授權
        if(authentication!=null&&!authentication.getPrincipal().equals("anonymousUser")&&
                authentication.isAuthenticated()) {

            String userName = (String)authentication.getPrincipal();
            //讀取使用者所有的Url,可以通過使用者服務拿到當前使用者服務拿到該使用者的能訪問的地址
            Set<String> urls = new HashSet<>();
            urls.add("/app/**");
            for (String  url : urls) {
                if (antPathMatcher.match(url, requestURI)) {
                    isPermission = true;
                    LOGGER.info("使用者[{}]鑑權,鑑權地址為:{}.",userName,requestURI);
                    break;
                }
            }
            return isPermission;
        }
        return isPermission;
    }
}

外部化配置

@ConfigurationProperties(prefix = "app.zuul")
public class ZuulProperties{

    private AuthProperties auth = new AuthProperties();

    private OAuth2Properties oauth = new OAuth2Properties();

    public OAuth2Properties getOauth() {
        return oauth;
    }

    public void setOauth(OAuth2Properties oauth) {
        this.oauth = oauth;
    }

    public AuthProperties getAuth() {
        return auth;
    }

    public void setAuth(AuthProperties auth) {
        this.auth = auth;
    }


}

public class AuthProperties {


    private List<String> methodGetUrl = Lists.newArrayList();
    private List<String> methodPostUrl = Lists.newArrayList("hello");

    public List<String> getMethodGetUrl() {
        return methodGetUrl;
    }

    public void setMethodGetUrl(List<String> methodGetUrl) {
        this.methodGetUrl = methodGetUrl;
    }

    public List<String> getMethodPostUrl() {
        return methodPostUrl;
    }

    public void setMethodPostUrl(List<String> methodPostUrl) {
        this.methodPostUrl = methodPostUrl;
    }

    public String[] toGetAdapter(){
        if(methodGetUrl.size() > 0 ){
            String[] toBeStored = methodGetUrl.toArray(new String[methodGetUrl.size()]);
            return toBeStored;
        }
        return new String[]{""};
    }
    public String[] toPostAdapter(){
        if(methodPostUrl.size() > 0 ){
            String[] toBeStored = methodPostUrl.toArray(new String[methodPostUrl.size()]);
            return toBeStored;
        }
        return new String[]{""};
    }

}

application.properties配置

##設定不攔截URI
app.zuul.auth.methodGetUrl = \
  /authentication/require,\
  /app/account/**
app.zuul.auth.methodPostUrl=/authentication/form,\
  /login
## 設定oauth2授權認證
app.zuul.oauth.clients[0].clientId = app
## 需要配置一個passwordEncoder我配置的是123456
app.zuul.oauth.clients[0].clientSecret = $2a$10$m9W7aCgZ8HwyMtP/CQY2mecC1k6Zjry28HBimMx2.5SuCJPv08C9y

配置加密類

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

啟動類

@SpringBootApplication
//@EnableDiscoveryClient
//@EnableZuulProxy
@EnableConfigurationProperties(ZuulProperties.class)
public class AppOauthApplicationBootstrap {

    public static void main(String[] args) {
        SpringApplication.run(AppOauthApplicationBootstrap.class,args);
    }

}

配置認證伺服器

###認證伺服器配置

@Configuration
@EnableAuthorizationServer
public class AppAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

   private final AuthenticationManager authenticationManager;
   private final UserDetailsService userDetailsService;
   private final ZuulProperties zuulProperties;
   private final TokenStore tokenStore ;
   @Autowired(required = false)
   private JwtAccessTokenConverter jwtAccessTokenConverter;
   @Autowired(required = false)
   private TokenEnhancer jwtTokenEnhancer;
   private final PasswordEncoder passwordEncoder;

   @Autowired
   public AppAuthorizationServerConfig(AuthenticationManager authenticationManager, UserDetailsService userDetailsService, ZuulProperties zuulProperties, TokenStore tokenStore, PasswordEncoder passwordEncoder) {
      this.authenticationManager = authenticationManager;
      this.userDetailsService = userDetailsService;
      this.zuulProperties = zuulProperties;
      this.tokenStore = tokenStore;
      this.passwordEncoder = passwordEncoder;
   }


   @Override
   public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
      InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
      OAuth2ClientProperties[] clientProperties = zuulProperties.getOauth().getClients();
      if(ArrayUtils.isNotEmpty(zuulProperties.getOauth().getClients())) {
         for (OAuth2ClientProperties oAuth2ClientProperties : clientProperties) {
             builder.withClient(oAuth2ClientProperties.getClientId())
               .secret(oAuth2ClientProperties.getClientSecret())
               //token有效時間
               .accessTokenValiditySeconds(oAuth2ClientProperties.getAccessTokenValiditySeconds())
               //驗證模式
               .