简体   繁体   中英

Spring Boot / Thymeleaf - Authentication Difficulties with Javascript Request and X.509 Auth

I have a Spring Boot web app that is configured to use .x509 certificate authentication.

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests().antMatchers("/h2-console/**", "/css/**", "/image/**", "/js/**").permitAll()
                .and().authorizeRequests().antMatchers("/**").authenticated().and().x509()
                .userDetailsService(userDetailsService).subjectPrincipalRegex("CN=(.*?),");

    }
}

UserDetailsService:

@Service
public class AppUserDetailsService implements UserDetailsService {

    @Autowired
    private AppUserRepo userRepo;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        var userVal = userRepo.getUser(username);
        if (!userVal.isPresent()) {
            throw new UsernameNotFoundException("No user with name: " + username);
        }
        var user = userVal.get();
        return new User(user.getUsername(), "",
                user.getRoles().stream().map(r -> new SimpleGrantedAuthority(r)).collect(Collectors.toList()));
    }

}

I have the app working for the most part; that is, I can access full pages requiring authorization with this setup. The issue arises when I attempt to load a fragment using JavaScript/TypeScript. When I click on the button that triggers the JS call, I receive the following error:

{"timestamp":"2019-09-17T14:08:44.019+0000","status":403,"error":"Forbidden","message":"Forbidden","path":"/employee/search"}

The JS/TS snippet

function loadEmployees() {

    const container = document.getElementById("resultPanel");
    const url = "employee/search";
    fetch(url, {
        method: "POST",
        headers: {
            'Content-Type': 'application/json',

        },
        body: JSON.stringify(buildBody())
    })
        .then(res => res.text())
        .then(res => container.innerHTML = res);

}

The template where the fragment is inserted:

<div id="page" class="container">

    <h1>Employee Search</h1>
    <div class="clearfix">
        <div class='form-row my-3'>
            <label for="reqType">Search Type: </label>
            <select class="form-control" name="reqType" id="reqType">
                <option>Select</option>
                <option value=1>SSN</option>
                <option value=2>Name</option>
            </select>
        </div>
     /// Code omitted for brevity
        <button class="btn btn-primary float-right" id="searchBtn">Search</button>
    </div>

The fragment itself:

<div th:fragment="results-list">
    <div class="row d-md-flex flex-header d-none">

        <div class="col-md-4">
            Name
        </div>
        <div class="col-md-2">
            Loc.
        </div>
        <div class="col-md-2">
            DB
        </div>
        <div class="col-md-1">

        </div>
    </div>
    <div class="row d-flex" th:each="emp : ${employees}">

        <div class="col-md-4">
            <span class="font-weight-bold d-inline d-md-none pr-1">Name: </span>
            <span th:text="${emp.lastName} + ', ' + ${emp.firstName} + ' ' + ${emp.middleName}"></span>
        </div>
        <div class=" col-md-2">
            <span class="font-weight-bold d-inline d-md-none pr-1">Loc.: </span>
            <span th:text="${emp.location}"></span>
        </div>
        <div class="col-md-2">
            <span class="font-weight-bold d-inline d-md-none pr-1">DB: </span>
            <span th:text="${emp.dbName}"></span>
        </div>
        <div class="col-md-1">
            <span class="font-weight-bold d-inline d-md-none pr-1">Edit: </span>
            <a th:href="@{'/employee/' + ${emp.id}}">View <i class="fas fa-edit"></i></a>

        </div>
    </div>
</div>

    <hr />
    <h2 class="mt-1">Results</h2>
    <div id="resultPanel" class="mt-4">

    </div>
</div>

..and the Controller that loads the fragment:

@Controller
@RequestMapping("/employee")
public class EmployeeController {

    @Autowired
    private EmployeeService empService;

    @PostMapping("/search")
    public String searchByName(@RequestBody SearchDTO dto, Model model) {
        model.addAttribute("employees", empService.searchEmployees(dto));
        return "fragments/results-list";
    }

Please note, I realize JavaScript and Java are very different languages; I am including both tags given that I am not positive whether the change must be performed client or server side. Feel free to remove one if you do not believe such is appropriate.

Thanks so much.

I had run into the same issue and found I needed to specify my secured resources before my open resources.

So you might have better luck with the following:

httpSecurity
    .csrf().disable()   
    .authorizeRequests().antMatchers("/**").authenticated().and().x509()
    .userDetailsService(userDetailsService).subjectPrincipalRegex("CN=(.*?),")
    .and()
    .authorizeRequests().antMatchers("/h2-console/**", "/css/**", "/image/**", "/js/**").permitAll()
    ;

See the documentation here .

Edited per feedback to make this a compiler friendly code sample.

Edit #2 - added .csrf().disable(). I've had to do this myself for REST to work, and see others have also.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM