springboot整合security+cas单点登陆

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

你可能感兴趣的:(springboot)