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();
    }
}