簡體   English   中英

為Rest Api實施Spring Security

[英]Implement Spring Security for Rest Api

我將以下代碼用於Rest API身份驗證:

@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        Optional<String> basicToken = Optional.ofNullable(request.getHeader(HttpHeaders.AUTHORIZATION))
                .filter(v -> v.startsWith("Basic"))
                .map(v -> v.split("\\s+")).filter(a -> a.length == 2).map(a -> a[1]);
        if (!basicToken.isPresent()) {
            return sendAuthError(response);
        }

        byte[] bytes = Base64Utils.decodeFromString(basicToken.get());
        String namePassword = new String(bytes, StandardCharsets.UTF_8);
        int i = namePassword.indexOf(':');
        if (i < 0) {
            return sendAuthError(response);
        }
        String name = namePassword.substring(0, i);
        String password = namePassword.substring(i + 1);
//        Optional<String> clientId = authenticationService.authenticate(name, password, request.getRemoteAddr());
        Merchants merchant = authenticationService.authenticateMerchant(name, password, request.getRemoteAddr());
        if (merchant == null) {
            return sendAuthError(response);
        }
        request.setAttribute(CURRENT_CLIENT_ID_ATTRIBUTE, merchant.getId());
        return true;
    }

我怎樣才能用Spring Security重寫代碼以獲得相同的結果,但是對於不同的鏈接進行身份驗證呢? 例如:

localhost:8080/v1/notification - requests should NOT be authenticated.
localhost:8080/v1/request - requests should be authenticated.

在這里您可以找到一個有效的項目https://github.com/angeloimm/springbasicauth

我知道在pom.xml文件中有很多無用的依賴項,但是我是從一個已經存在的項目開始的,我沒有時間去破壞它

基本上,您必須:

  • 配置春季安全
  • 配置Spring MVC
  • 根據spring安全性實現您自己的身份驗證提供程序。 注意我使用了inMemoryAuthentication。 請根據自己的意願進行修改

讓我解釋一下代碼。

Spring MVC配置

@Configuration
@EnableWebMvc
@ComponentScan(basePackages= {"it.olegna.test.basic"})
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(final List<HttpMessageConverter<?>> converters) {
        converters.add(new MappingJackson2HttpMessageConverter());
    }
}

在這里,我們沒有做任何其他事情來配置spring MVC,方法是告訴它在哪里可以找到控制器等等,並使用單個消息轉換器。 MappingJackson2HttpMessageConverter以便產生JSON響應

春季安全配置

@Configuration
@EnableWebSecurity
@Import(value= {WebMvcConfig.class})
public class WebSecConfig extends WebSecurityConfigurerAdapter {
     @Autowired private RestAuthEntryPoint authenticationEntryPoint;

        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth
              .inMemoryAuthentication()
              .withUser("test")
              .password(passwordEncoder().encode("testpwd"))
              .authorities("ROLE_USER");
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
              .authorizeRequests()
              .antMatchers("/securityNone")
              .permitAll()
              .anyRequest()
              .authenticated()
              .and()
              .httpBasic()
              .authenticationEntryPoint(authenticationEntryPoint);
        }
        @Bean
        public PasswordEncoder passwordEncoder() {
            return NoOpPasswordEncoder.getInstance();
        }
}

在這里,我們配置Spring Security,以便對所有以securityNone開頭的請求使用HTTP基本認證。 我們使用NoOpPasswordEncoder來編碼提供的密碼。 這個PasswrodEncoder絕對不做任何事情……它保持原樣。

RestEntryPoint

@Component
public class RestAuthEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,  AuthenticationException authException) throws IOException, ServletException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    }
}

該入口點禁用所有不包含身份驗證標頭的請求

SimpleDto :一個非常簡單的DTO,代表控制器的JSON答案

public class SimpleDto implements Serializable {

    private static final long serialVersionUID = 1616554176392794288L;
    private String simpleDtoName;

    public SimpleDto() {
        super();
    }
    public SimpleDto(String simpleDtoName) {
        super();
        this.simpleDtoName = simpleDtoName;
    }
    public String getSimpleDtoName() {
        return simpleDtoName;
    }
    public void setSimpleDtoName(String simpleDtoName) {
        this.simpleDtoName = simpleDtoName;
    }

}

TestBasicController :一個非常簡單的控制器

@RestController
@RequestMapping(value= {"/rest"})
public class TestBasicController {
    @RequestMapping(value= {"/simple"}, method= {RequestMethod.GET}, produces= {MediaType.APPLICATION_JSON_UTF8_VALUE})
    public ResponseEntity<List<SimpleDto>> getSimpleAnswer()
    {
        List<SimpleDto> payload = new ArrayList<>();
        for(int i= 0; i < 5; i++)
        {
            payload.add(new SimpleDto(UUID.randomUUID().toString()));
        }
        return ResponseEntity.ok().body(payload);
    }
}

因此,如果您使用郵遞員或任何其他測試人員嘗試此項目,則可能有兩種情況:

  • 需要認證
  • 一切都好

假設您要調用URL http:// localhost:8080 / test_basic / rest / simple而不傳遞Authentication頭。 HTTP狀態代碼將為401 Unauthorized

這意味着需要身份驗證標頭

通過將此標頭添加到請求中, Authorization Basic dGVzdDp0ZXN0cHdk都可以正常工作。請注意,字符串dGVzdDp0ZXN0cHdk是字符串username:password的Base64編碼; 在我們的例子中,是test:testpwd定義的test:testpwd的Base64編碼

我希望這是有用的

安傑洛

網絡安全用戶數據服務

為了配置Spring安全性以從數據庫檢索用戶詳細信息,您必須執行以下操作:

創建一個org.springframework.security.core.userdetails.UserDetailsS​​ervice實現,如下所示:

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private BasicService svc;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        BasicUser result = svc.findByUsername(username);
        if( result == null )
        {
            throw new UsernameNotFoundException("No user found with username "+username);
        }
        return result;
    }

}

將其注入到spring安全配置中,並像這樣使用它:

public class WebSecConfig extends WebSecurityConfigurerAdapter {
    @Autowired private RestAuthEntryPoint authenticationEntryPoint;
    @Autowired
    UserDetailsService userDetailsService;
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//      auth
//      .inMemoryAuthentication()
//      .withUser("test")
//      .password(passwordEncoder().encode("testpwd"))
//      .authorities("ROLE_USER");
        auth.userDetailsService(userDetailsService);
        auth.authenticationProvider(authenticationProvider());
    }
    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        return authenticationProvider;
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
        .authorizeRequests()
        .antMatchers("/securityNone")
        .permitAll()
        .anyRequest()
        .authenticated()
        .and()
        .httpBasic()
        .authenticationEntryPoint(authenticationEntryPoint);
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

我在提供的github鏈接上推送了代碼。 在那里,您可以找到基於以下內容的完整工作示例:

  • 春天5
  • 春季安全5
  • 過冬
  • h2數據庫

隨時根據自己的情況進行調整

您可以使用在baeldung.commkyong.com等各種網站上描述的默認spring-security配置。 您的示例中的技巧似乎是致電來獲取Merchant 根據authenticationServiceMerchant對象的復雜性,您可以使用以下代碼,也可以實現外觀以獲取類似的行為。

@Autowired
public void authenticationManager(AuthenticationManagerBuilder auth) {
    auth.authenticationProvider(new AuthenticationProvider() {
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            Merchants merchant = authenticationService.authenticateMerchant(name, password, request.getRemoteAddr());
            if(merchant == null) {
                throw new AuthenticationException("No Merchant found.");
            }
            return new UsernamePasswordAuthenticationToken(name, password, merchant.getAuthorities());
        }

        @Override
        public boolean supports(Class<?> authentication) {
            return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
        }
    });
}

如果需要,可以通過一個單獨的過濾器在請求上設置屬性,該過濾器將從SecurityContext獲取Principal並將其作為屬性放入請求中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM