什么是PAC4J?
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
pac4j是一个支持多种支持多种协议的身份认证的Java客户端。
2,pac4j的12种客户端认证机制:目前我只有用过第一和第八种。
pac4j-oauth
module pac4j-cas
module pac4j-http
module pac4j-openid
module pac4j-saml
module pac4j-gae
module pac4j-oidc
module pac4j-jwt
module pac4j-ldap
module pac4j-sql
module pac4j-mongo
module pac4j-stormpath
module.
3,maven配置
1 <dependency> 2 <groupId>org.pac4j</groupId> 3 <artifactId>spring-security-pac4j</artifactId> 4 <version>1.4.3</version> 5 </dependency> 6 <dependency> 7 <groupId>org.pac4j</groupId> 8 <artifactId>pac4j-oauth</artifactId> 9 <version>1.8.8</version> 10 </dependency> View Code
主要是三个jar包:
pac4j是一个简单而强大的安全引擎,用于Java对用户进行身份验证、获取其配置文件和管理授权,以确保web应用程序安全。它提供了一套完整的概念和组件。它基于Java 8,并在Apache 2许可下使用。它可用于大多数框架/工具和支持大多数认证/授权机制。
已经集成可用的场景
身份验证机制
授权机制
项目地址: https://github.com/pac4j/pac4j/tree/pac4j-3.1.0
———————-
pom.xml
<?xml version=”1.0″ encoding=”UTF-8″?>
<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.0</modelVersion>
<groupId>org.ikane</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-boot-pac4j-demo</name>
<description>Spring-boot PAC4J DEMO</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.8.RELEASE</version>
<relativePath/> <!– lookup parent from repository –>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>spring-security-pac4j</artifactId>
<version>1.4.1</version>
<exclusions>
<exclusion>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-cas</artifactId>
<version>1.8.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Pac4jConfig.java
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;
}
}
SecurityConfig.java
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();
}
}
SpringBootPac4jDemoApplication.java
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);
}
}
ThymeleafConfig.java
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();
}
}
IndexController.java
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”;
}
}
LoginController.java
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);
}
}
ClientUserDetails.java
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<GrantedAuthority> authorities;
String password;
public ClientUserDetails() {
// TODO Auto-generated constructor stub
}
public ClientUserDetails(String username, String providerId, Collection<GrantedAuthority> 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<GrantedAuthority> authorities) {
this.authorities = authorities;
}
public void setPassword(String password) {
this.password = password;
}
ClientUserDetailsService.java
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<ClientAuthenticationToken> {
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<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
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;
}
AccountService.java
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<Object,Object> map = new HashMap<>();
/**
*
List results = jdbcTemplate.query(
“select * from account where provider = ? and provider_user_id = ?”,
[providerName, providerUserId] as Object[],
new GenericRowMapper()
)
if (results.size() > 1) {
throw new Exception(“multiple accounts by provider [${providerName}] for id [${providerUserId}]”)
}
* **/
return map;
}
public Boolean createAccountForProvider(String providerName, String providerUserId, String displayName) {
/**
* log.debug(“creating new account for displayName=${displayName} using provider=${providerName} with id ${providerUserId}”)
int result = jdbcTemplate.update(
“insert into account (display_name, provider, provider_user_id) values (?, ?, ?)”,
displayName,
providerName,
providerUserId
)
if (result != 1) {
log.warn(“creation of account for provider [${providerName}] and id [${providerUserId}] failed”)
return false
}
* */
return true;
}
}
index.html
<!doctype html>
<html xmlns:th=”http://www.thymeleaf.org”>
<head>
<title>Spring Pac4j Demo</title>
</head>
<body>
<h2>Index Page sgdsfg</h2>
</body>
</html>123456789
login.html
<!doctype html>
<html xmlns:th=”http://www.thymeleaf.org”
xmlns:layout=”http://www.ultraq.net.nz/thymeleaf/layout”
layout:decorator=”layouts/default”>
<head>
<title>Login</title>
</head>
<body>
<div id=”content” class=”sign-in-page” layout:fragment=”content”>
<h2>Sign In</h2>
<a th:href=”${casAuthUrl}” th:class=”‘oauth-login-link cas-login'”>CAS</a>
<a th:href=”${gitHubAuthUrl}” th:class=”‘oauth-login-link github-login'”>GitHub</a>
<a th:href=”${google2AuthUrl}” th:class=”‘oauth-login-link google-login'”>Google</a>
<a th:href=”${twitterAuthUrl}” th:class=”‘oauth-login-link twitter-login'”>Twitter</a>
</div>
</body>
</html>