Java Spring Boot Integration Guide
Complete guide for integrating Java Spring Boot applications with CAS SSO
Setup time: 8 minutes
Difficulty: Intermediate
Spring Boot 2.7+
1. Dependencies (Maven)
<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>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
</dependencies>
2. Configuration Properties
# application.yml
cas:
server-url: http://localhost:5000
client-id: your_client_id
client-username: your_client_username
client-password: your_client_password
signature-secret: your_signature_secret
callback-url: http://localhost:8080/cas/callback
token-expiration: 120
spring:
session:
timeout: 7200s
3. CAS Client Service
// CasClientService.java
@Service
public class CasClientService {
@Value("${cas.server-url}")
private String serverUrl;
@Value("${cas.client-id}")
private String clientId;
@Value("${cas.client-username}")
private String clientUsername;
@Value("${cas.client-password}")
private String clientPassword;
@Value("${cas.signature-secret}")
private String signatureSecret;
@Value("${cas.callback-url}")
private String callbackUrl;
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
public CasClientService() {
this.restTemplate = new RestTemplate();
this.objectMapper = new ObjectMapper();
}
public String getLoginUrl(String returnUrl) {
String loginUrl = serverUrl + "/auth/login";
String encodedCallbackUrl = URLEncoder.encode(
callbackUrl + "?return_url=" + URLEncoder.encode(returnUrl, StandardCharsets.UTF_8),
StandardCharsets.UTF_8
);
return loginUrl + "?callback_url=" + encodedCallbackUrl;
}
public CasUser validateToken(String token) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(signatureSecret.getBytes())
.build()
.parseClaimsJws(token)
.getBody();
return CasUser.builder()
.username(claims.getSubject())
.email(claims.get("email", String.class))
.role(claims.get("role", String.class))
.firstName(claims.get("first_name", String.class))
.lastName(claims.get("last_name", String.class))
.build();
} catch (Exception e) {
throw new InvalidTokenException("Token validation failed", e);
}
}
public AuthResult authenticate(String username, String password) {
try {
Map<String, Object> requestBody = Map.of(
"username", username,
"password", password,
"client_id", clientId,
"client_username", clientUsername,
"client_password", clientPassword
);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
ResponseEntity<String> response = restTemplate.postForEntity(
serverUrl + "/api/sso/token",
request,
String.class
);
return objectMapper.readValue(response.getBody(), AuthResult.class);
} catch (Exception e) {
throw new AuthenticationException("Authentication failed", e);
}
}
}
4. Security Configuration
// SecurityConfig.java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/", "/cas/**", "/login", "/error").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.permitAll()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1)
.maxSessionsPreventsLogin(false)
)
.csrf(csrf -> csrf.disable());
return http.build();
}
}
5. Model Classes
// CasUser.java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CasUser {
private String username;
private String email;
private String role;
private String firstName;
private String lastName;
public String getFullName() {
return firstName + " " + lastName;
}
public boolean isAdmin() {
return "admin".equals(role);
}
public boolean hasRole(String role) {
return this.role.equals(role);
}
}
// AuthResult.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AuthResult {
private String token;
private CasUser user;
private LocalDateTime expiresAt;
}
6. Controller Examples
// HomeController.java
@Controller
public class HomeController {
private final CasClientService casClientService;
public HomeController(CasClientService casClientService) {
this.casClientService = casClientService;
}
@GetMapping("/")
public String index(Model model, HttpSession session) {
CasUser user = (CasUser) session.getAttribute("casUser");
model.addAttribute("user", user);
return "index";
}
@GetMapping("/dashboard")
public String dashboard(Model model, HttpSession session) {
CasUser user = (CasUser) session.getAttribute("casUser");
if (user == null) {
return "redirect:/login";
}
model.addAttribute("user", user);
return "dashboard";
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin")
public String admin(Model model, HttpSession session) {
CasUser user = (CasUser) session.getAttribute("casUser");
model.addAttribute("user", user);
return "admin";
}
@GetMapping("/login")
public String login(@RequestParam(required = false) String returnUrl) {
if (returnUrl == null) {
returnUrl = "/dashboard";
}
String loginUrl = casClientService.getLoginUrl(returnUrl);
return "redirect:" + loginUrl;
}
@GetMapping("/cas/callback")
public String casCallback(
@RequestParam String token,
@RequestParam(required = false) String return_url,
HttpSession session
) {
try {
CasUser user = casClientService.validateToken(token);
session.setAttribute("casUser", user);
session.setAttribute("casToken", token);
return "redirect:" + (return_url != null ? return_url : "/dashboard");
} catch (Exception e) {
return "redirect:/login?error=authentication_failed";
}
}
@GetMapping("/logout")
public String logout(HttpSession session) {
session.invalidate();
return "redirect:/";
}
}
7. Custom Authentication
// @CasAuth annotation
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CasAuth {
String[] roles() default {};
}
// CasAuthAspect.java
@Aspect
@Component
public class CasAuthAspect {
@Around("@@annotation(casAuth)")
public Object checkAuthentication(ProceedingJoinPoint joinPoint, CasAuth casAuth) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
HttpSession session = request.getSession();
CasUser user = (CasUser) session.getAttribute("casUser");
if (user == null) {
// Redirect to login
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse();
response.sendRedirect("/login?returnUrl=" + request.getRequestURI());
return null;
}
// Check roles if specified
String[] requiredRoles = casAuth.roles();
if (requiredRoles.length > 0) {
boolean hasRole = Arrays.stream(requiredRoles)
.anyMatch(role -> user.hasRole(role));
if (!hasRole) {
throw new AccessDeniedException("Insufficient permissions");
}
}
return joinPoint.proceed();
}
}