参考:http://blog.csdn.net/cl_andywin/article/details/53998986
创建application.properties文件,加入以下内容:
#CAS服务地址
cas.server.host.url=http://cas.XXXX.net/cas
#CAS服务登录地址
cas.server.host.login_url=${cas.server.host.url}/login
#CAS服务登出地址
cas.server.host.logout_url=${cas.server.host.url}/logout?service=${app.server.host.url}
#应用访问地址
app.server.host.url=http://localhost:8080
#应用登录地址
app.login.url=/admin/index.html
#应用登出地址
app.logout.url=/logout
#应用服务名称
app.server.name=http://localhost:8080
security配置文件SecurityConfig,项目启动的时候会执行,初始化security和cas的设置
/**
* @author mu.shuntao
* @create 2017-04-10 10:44
*/
@Configuration
@EnableWebSecurity //禁用Boot的默认Security配置,配合@Configuration启用自定义配置(需要扩展WebSecurityConfigurerAdapter)
@EnableGlobalMethodSecurity(prePostEnabled = true) //启用Security注解,例如最常用的@PreAuthorize
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CasProperties casProperties;
/**
* configure(AuthenticationManagerBuilder): 身份验证配置,用于注入自定义身份验证Bean和密码校验规则
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
auth.authenticationProvider(casAuthenticationProvider());
}
/**
* configure(WebSecurity): Web层面的配置,一般用来配置无需安全检查的路径
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/**", "/templates/**");
}
/**
* configure(HttpSecurity): Request层面的配置,对应XML Configuration中的元素
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()//配置安全策略
.antMatchers("/").permitAll()//所有请求都不需要验证
//.antMatchers("/admin/**").authenticated()//admin下请求需要验证
.and()
.logout()
.permitAll()//定义logout不需要验证
.and()
.formLogin();
http.exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint())
.and()
.addFilter(casAuthenticationFilter())
.addFilterBefore(casLogoutFilter(), LogoutFilter.class)
.addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class);
http.csrf().disable();
// 关闭spring security默认的frame访问限制
http.headers().frameOptions().sameOrigin();
}
@Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();
casAuthenticationEntryPoint.setLoginUrl(casProperties.getCasServerLoginUrl());
casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
return casAuthenticationEntryPoint;
}
/**
* 指定service相关信息
*/
@Bean
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService(casProperties.getAppServerUrl() + casProperties.getAppLoginUrl());
serviceProperties.setAuthenticateAllArtifacts(true);
return serviceProperties;
}
/**
* CAS认证过滤器
*/
@Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
casAuthenticationFilter.setFilterProcessesUrl(casProperties.getAppLoginUrl());
return casAuthenticationFilter;
}
/**
* cas 认证 Provider
*/
@Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setAuthenticationUserDetailsService(customUserDetailsService());
//casAuthenticationProvider.setUserDetailsService(customUserDetailsService()); //这里只是接口类型,实现的接口不一样,都可以的。
casAuthenticationProvider.setServiceProperties(serviceProperties());
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setKey("casAuthenticationProviderKey");
return casAuthenticationProvider;
}
/* @Bean
public UserDetailsService customUserDetailsService(){
return new CustomUserDetailsService();
}*/
//用户自定义的AuthenticationUserDetailsService
@Bean
public AuthenticationUserDetailsService customUserDetailsService() {
return new CustomUserDetailsService();
}
@Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
return new Cas20ServiceTicketValidator(casProperties.getCasServerUrl());
}
/**
* 单点登出过滤器
*/
@Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
singleSignOutFilter.setCasServerUrlPrefix(casProperties.getCasServerUrl());
singleSignOutFilter.setIgnoreInitConfiguration(true);
return singleSignOutFilter;
}
/**
* 请求单点退出过滤器
*/
@Bean
public LogoutFilter casLogoutFilter() {
LogoutFilter logoutFilter = new LogoutFilter(casProperties.getCasServerLogoutUrl(), new SecurityContextLogoutHandler());
logoutFilter.setFilterProcessesUrl(casProperties.getAppLogoutUrl());
return logoutFilter;
}
}
这里在指定service里设置的是,就是http://localhost:8080/admin/index.html路径,保证这个路径能正常返回,登陆成功后才能正常返回ticket,执行后面的CustomUserDetailsService类中的loadUserDetails方法
serviceProperties.setService(casProperties.getAppServerUrl() + casProperties.getAppLoginUrl());
User类,用于封装登陆成功后的用户信息,登陆成功后程序里获取用户信息可以使用
User userDetails = (User) SecurityContextHolder.getContext()
.getAuthentication()
.getPrincipal();
String username = userDetails.getUsername();
String id = userDetails.getId();
页面获取用户信息可以使用,如下为获取用户名
${Session.SPRING_SECURITY_CONTEXT.authentication.principal.username}
/**
* @author mu.shuntao
* @create 2017-04-10 13:59
*/
public class User implements UserDetails{
/**
* 用户ID
*/
private String id;
/**
* 用户名称
*/
private String name;
/**
* 登录名称
*/
private String username;
/**
* 登录密码
*/
private String password;
private boolean isAccountNonExpired = true; //是否过期
private boolean isAccountNonLocked = true; //账户未锁定为true
private boolean isCredentialsNonExpired = true; //证书不过期为true
private boolean isEnabled = true; //是否可用
private Set authorities = new HashSet();
@Override
public Collection extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return isAccountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return isAccountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return isCredentialsNonExpired;
}
@Override
public boolean isEnabled() {
return isEnabled;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setAccountNonExpired(boolean accountNonExpired) {
isAccountNonExpired = accountNonExpired;
}
public void setAccountNonLocked(boolean accountNonLocked) {
isAccountNonLocked = accountNonLocked;
}
public void setCredentialsNonExpired(boolean credentialsNonExpired) {
isCredentialsNonExpired = credentialsNonExpired;
}
public void setEnabled(boolean enabled) {
isEnabled = enabled;
}
}
CustomUserDetailsService 类,cas服务登陆成功后会自动执行loadUserDetails方法,我的cas服务返回了所有用户信息,直接从json里取出用户信息封装到User里。如果cas服务只返回用户id,可以拿着id去数据库查用户信息。
/**
* @author mu.shuntao
* @create 2017-04-10 13:54
*/
public class CustomUserDetailsService implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken> {
@Override
public UserDetails loadUserDetails(CasAssertionAuthenticationToken token) throws UsernameNotFoundException {
User user = new User();
Map userAttributess = token.getAssertion().getPrincipal().getAttributes();
if (userAttributess != null) {
String userInfoJson = String.valueOf(userAttributess.get("userInfo"));
if (StringUtils.isNotEmpty(userInfoJson)) {
JSONObject userInfo = JSONObject.parseObject(userInfoJson);
String userAppJson = String.valueOf(userAttributess.get("userApp"));
JSONArray jsonArray = JSONObject.parseArray(userAppJson);
if (jsonArray != null && jsonArray.size() > 0) {
boolean canLogin = false;
for (int i = 0; i < jsonArray.size(); i++) {
JSONObject appInfo = jsonArray.getJSONObject(i);
//判断用户是否有该应用登陆权限
if (appInfo.get("appId").equals("abc")) {
canLogin = true;
break;
}
}
// 如果可以登录系统则返回用户数据
if (canLogin) {
user.setUsername(userInfo.getString("userName"));
user.setName(userInfo.getString("userRealName"));
user.setPassword(userInfo.getString("password"));
user.setId(userInfo.getString("userId"));
}
}
}
}
return user;
}
}
最后附上cas服务端给客服端返回自定义数据的方法,自定义一个类UserInfoPrincipalResolver ,实现PrincipalResolver 接口,主要是复写resolve方法
,在resolve方法里封装数据
public class UserInfoPrincipalResolver implements PrincipalResolver {
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
/**
* Factory to create the principal type.
**/
@NotNull
protected PrincipalFactory principalFactory = new DefaultPrincipalFactory();
private PasswordEncoder passwordEncoder = new PlainTextPasswordEncoder();
/**
* Optional principal attribute name.
*/
protected String principalAttributeName;
@Override
public boolean supports(final Credential credential) {
return true;
}
@Override
public Principal resolve(final Credential credential) {
UsernamePasswordCredential captchaCredential = (UsernamePasswordCredential) credential;
final String principalId = extractPrincipalId(credential);
if (principalId == null) {
return null;
}
final Pair> pair = convertPersonAttributesToPrincipal(principalId, captchaCredential);
return this.principalFactory.createPrincipal(pair.getFirst(), pair.getSecond());
}
/**
* 设置传递给客户端的数据
*
* @param extractedPrincipalId
* @return
*/
protected Pair> convertPersonAttributesToPrincipal(final String extractedPrincipalId, UsernamePasswordCredential credential) {
final Map convertedAttributes = new HashMap<>();
String principalId = extractedPrincipalId;
String username = credential.getUsername();
String encryptedPassword = this.getPasswordEncoder().encode(credential.getPassword());
if ("MD5".equals(credential.getPasswordFlag())) {
encryptedPassword = credential.getPassword();
}
UserEntity userEntityParam = new UserEntity();
userEntityParam.setUserName(username);
userEntityParam.setPassword(encryptedPassword);
UserEntity userEntity = this.userService.checkUserLogin(userEntityParam);
userEntity = this.userService.getUserInfo(userEntity.getUserId());
UserAppEntity userAppEntity = new UserAppEntity();
userAppEntity.setUserId(userEntity.getUserId());
List userAppEntityList = this.userService.findUserAppList(userAppEntity);
convertedAttributes.put("userInfo", JSONObject.toJSONString(userEntity));
convertedAttributes.put("userApp", JSONObject.toJSONString(userAppEntityList));
return new Pair<>(principalId, convertedAttributes);
}
public void setPrincipalFactory(final PrincipalFactory principalFactory) {
this.principalFactory = principalFactory;
}
/**
* Sets the PasswordEncoder to be used with this class.
*
* @param passwordEncoder the PasswordEncoder to use when encoding
* passwords.
*/
public void setPasswordEncoder(final PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
/**
* Method to return the PasswordEncoder to be used to encode passwords.
*
* @return the PasswordEncoder associated with this class.
*/
private PasswordEncoder getPasswordEncoder() {
return this.passwordEncoder;
}
protected String extractPrincipalId(final Credential credential) {
return credential.getId();
}
}