pac4j是一个简单而强大的安全引擎,用于Java对用户进行身份验证、获取其配置文件和管理授权,以确保web应用程序安全。它提供了一套完整的概念和组件。它基于Java 8,并在Apache 2许可下使用。它可用于大多数框架/工具和支持大多数认证/授权机制。
J2E • Spring Web MVC (Spring Boot) • Spring Security (Spring Boot) • Apache Shiro
Play 2.x • Vertx • Spark Java • Ratpack • Undertow
CAS server • JAX-RS • Dropwizard • Apache Knox • Jooby
OAuth (Facebook, Twitter, Google…) - SAML - CAS - OpenID Connect - HTTP - OpenID - Google App Engine - Kerberos (SPNEGO/Negotiate)
LDAP - SQL - JWT - MongoDB - CouchDB - IP address - REST API
Roles/permissions - Anonymous/remember-me/(fully) authenticated - Profile type, attribute
CORS - CSRF - Security headers - IP address, HTTP method
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>org.ikanegroupId>
<artifactId>demoartifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>jarpackaging>
<name>spring-boot-pac4j-demoname>
<description>Spring-boot PAC4J DEMOdescription>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>1.2.8.RELEASEversion>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<java.version>1.7java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-springsecurity4artifactId>
<version>2.1.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.pac4jgroupId>
<artifactId>spring-security-pac4jartifactId>
<version>1.4.1version>
<exclusions>
<exclusion>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-webartifactId>
exclusion>
<exclusion>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-configartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.pac4jgroupId>
<artifactId>pac4j-casartifactId>
<version>1.8.5version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
package org.ikane;
import org.ikane.security.ClientUserDetailsService;
import org.ikane.service.AccountService;
import org.pac4j.cas.client.CasClient;
import org.pac4j.core.client.Clients;
import org.pac4j.springframework.security.authentication.ClientAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Pac4jConfig {
public static String CAS_LOGIN_URL = "https://casserverpac4j.herokuapp.com/login";
@Value("${oauth.callback.url}")
private String oauthCallbackUrl;
@Autowired
AccountService accountService;
@Bean
CasClient casClient() {
return new CasClient(CAS_LOGIN_URL);
}
@Bean
Clients clients() {
return new Clients(oauthCallbackUrl, casClient());
}
@Bean
ClientUserDetailsService clientUserDetailsService() {
ClientUserDetailsService clientUserDetailsService = new ClientUserDetailsService();
clientUserDetailsService.setAccountService(accountService);
return clientUserDetailsService;
}
@Bean
ClientAuthenticationProvider clientProvider() {
ClientAuthenticationProvider clientAuthenticationProvider = new ClientAuthenticationProvider();
clientAuthenticationProvider.setClients(clients());
clientAuthenticationProvider.setUserDetailsService(clientUserDetailsService());
return clientAuthenticationProvider;
}
}
package org.ikane;
import org.pac4j.core.client.Clients;
import org.pac4j.springframework.security.authentication.ClientAuthenticationProvider;
import org.pac4j.springframework.security.web.ClientAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true, securedEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
ApplicationContext context;
@Autowired
Clients clients;
@Autowired
ClientAuthenticationProvider clientProvider;
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers(
"/**/*.css",
"/**/*.png",
"/**/*.gif",
"/**/*.jpg",
"/**/*.ico",
"/**/*.js"
);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.permitAll()
;
http.addFilterBefore(clientFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
auth.authenticationProvider(clientProvider);
}
ClientAuthenticationFilter clientFilter() {
String suffixUrl="/*";
ClientAuthenticationFilter clientAuthenticationFilter = new ClientAuthenticationFilter(suffixUrl);
clientAuthenticationFilter.setClients(clients);
clientAuthenticationFilter.setSessionAuthenticationStrategy(sas());
//clientAuthenticationFilter.setAuthenticationManager((AuthenticationManager)clientProvider);
return clientAuthenticationFilter;
/*
return new ClientAuthenticationFilter(
clients: clients,
sessionAuthenticationStrategy: sas(),
authenticationManager: clientProvider as AuthenticationManager
)
*/
}
@Bean
SessionAuthenticationStrategy sas() {
return new SessionFixationProtectionStrategy();
}
}
package org.ikane;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootPac4jDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootPac4jDemoApplication.class, args);
}
}
package org.ikane;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect;
@Configuration
public class ThymeleafConfig {
@Bean
public SpringSecurityDialect springSecurityDialect() {
return new SpringSecurityDialect();
}
}
package org.ikane.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
class IndexController {
@RequestMapping("/")
@PreAuthorize("isAuthenticated()")
public String index() {
return "index";
}
}
package org.ikane.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.pac4j.cas.client.CasClient;
import org.pac4j.core.client.BaseClient;
import org.pac4j.core.client.Clients;
import org.pac4j.core.context.J2EContext;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.exception.RequiresHttpAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class LoginController {
Logger logger = LoggerFactory.getLogger(LoginController.class);
@Autowired
private Clients clients;
@RequestMapping("/login")
public String login(HttpServletRequest request, HttpServletResponse response, Model model) {
if (isAuthenticated()) {
return "redirect:/";
}
final WebContext context = new J2EContext(request, response);
//定义cas客户端
final CasClient casClient = (CasClient) clients.findClient(CasClient.class);
model.addAttribute("casAuthUrl", getClientLocation(casClient, context));
return "login";
}
//获取客户端的链接
public String getClientLocation(BaseClient client, WebContext context) {
try {
return ((CasClient)client).getRedirectAction(context, false).getLocation();
} catch (RequiresHttpAction e) {
e.printStackTrace();
logger.error("error", e);
return null;
}
//return client.getRedirectAction(context, false, false).getLocation();
}
protected boolean isAuthenticated() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return !(auth instanceof AnonymousAuthenticationToken);
}
}
package org.ikane.security;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
class ClientUserDetails implements UserDetails {
private static final long serialVersionUID = 6523314653561682296L;
String username;
String providerId;
Collection authorities;
String password;
public ClientUserDetails() {
// TODO Auto-generated constructor stub
}
public ClientUserDetails(String username, String providerId, Collection authorities) {
super();
this.username = username;
this.providerId = providerId;
this.authorities = authorities;
}
@Override
public Collection extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
// TODO Auto-generated method stub
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
public void setUsername(String username) {
this.username = username;
}
public void setAuthorities(Collection authorities) {
this.authorities = authorities;
}
public void setPassword(String password) {
this.password = password;
}
}
package org.ikane.security;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.ikane.service.AccountService;
import org.pac4j.springframework.security.authentication.ClientAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public class ClientUserDetailsService implements AuthenticationUserDetailsService {
private AccountService accountService;
public UserDetails loadUserDetails(final ClientAuthenticationToken token) throws UsernameNotFoundException {
Map account = accountService.lookupAccountByProvider(token.getClientName(), token.getUserProfile().getId());
//String username = account.containsKey("displayName") ? account.displayName : ""
String username = "admin";
final List authorities = new ArrayList();
for (String role: token.getUserProfile().getRoles()) {
authorities.add(new SimpleGrantedAuthority(role));
}
if (!account.isEmpty() && authorities.isEmpty()) {
// default to user role
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
}
return new ClientUserDetails(username, token.getUserProfile().getId(), authorities);
}
public AccountService getAccountService() {
return accountService;
}
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
}
package org.ikane.service;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Service;
@Service
public class AccountService {
/**
* @Autowired
JdbcTemplate jdbcTemplate
* */
public Map lookupAccountByProvider(String providerName, String providerUserId) {
HashMap
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring Pac4j Demotitle>
head>
<body>
<h2>Index Page sgdsfgh2>
body>
html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="layouts/default">
<head>
<title>Logintitle>
head>
<body>
<div id="content" class="sign-in-page" layout:fragment="content">
<h2>Sign Inh2>
<a th:href="${casAuthUrl}" th:class="'oauth-login-link cas-login'">CASa>
<a th:href="${gitHubAuthUrl}" th:class="'oauth-login-link github-login'">GitHuba>
<a th:href="${google2AuthUrl}" th:class="'oauth-login-link google-login'">Googlea>
<a th:href="${twitterAuthUrl}" th:class="'oauth-login-link twitter-login'">Twittera>
div>
body>
html>