Reputation: 2522
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.
Upvotes: 0
Views: 266
Reputation: 1506
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.
Upvotes: 1