Spring Security的几个重要词

1.SecurityContextHolder:是安全上下文容器,能够在此得知操做的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保存在SecurityContextHolder中。

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();

 

上面的代码是经过SecurityContextHolder来获取到信息,其中getAuthentication()返回了认证信息,再次getPrincipal()返回了身份信息,UserDetails即是Spring对身份信息封装的一个接口。

2.Authentication:源码以下:

复制代码
package org.springframework.security.core;

import java.io.Serializable;
import java.security.Principal;
import java.util.Collection;

public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    Object getCredentials();

    Object getDetails();

    Object getPrincipal();

    boolean isAuthenticated();

    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
复制代码

 

  • getAuthorities(),权限信息列表,默认是GrantedAuthority接口的一些实现类,一般是表明权限信息的一系列字符串。
  • getCredentials(),密码信息,用户输入的密码字符串,在认证事后一般会被移除,用于保障安全。
  • getDetails(),细节信息,web应用中的实现接口一般为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值。
  • getPrincipal(),敲黑板!!!最重要的身份信息,大部分状况下返回的是UserDetails接口的实现类,也是框架中的经常使用接口之一。


3.AuthenticationManager:顾名思义,它是认证的一个管理者他是一个接口,里面有个方法authenticate接受Authentication这个参数来完成验证;
4.ProviderManager实现AuthenticationManager这个接口,完成验证工做。部分源码:

复制代码
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
        InitializingBean {
    // 维护一个AuthenticationProvider列表
    private List<AuthenticationProvider> providers = Collections.emptyList();
 
    
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    Authentication result = null;
    boolean debug = logger.isDebugEnabled();
    Iterator var6 = this.getProviders().iterator();
   //依次来认证
    while(var6.hasNext()) {
        AuthenticationProvider provider = (AuthenticationProvider)var6.next();
        if (provider.supports(toTest)) {
            if (debug) {
                logger.debug("Authentication attempt using " + provider.getClass().getName());
            }

            try {
         // 若是有Authentication信息,则直接返回
                result = provider.authenticate(authentication);
                if (result != null) {
                    this.copyDetails(authentication, result);
                    break;
                }
            } catch (AccountStatusException var11) {
                this.prepareException(var11, authentication);
                throw var11;
            } catch (InternalAuthenticationServiceException var12) {
                this.prepareException(var12, authentication);
                throw var12;
            } catch (AuthenticationException var13) {
                lastException = var13;
            }
        }
    }
}
复制代码

 

5.DaoAuthenticationProvider:它是AuthenticationProvider的的一个实现类,很是重要,它主要完成了两个工做,

一个是retrieveUser方法,它返回UserDetails类,看看它的源码:

复制代码
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    UserDetails loadedUser;
    try {
     //记住loadUserByUsername这个方法;
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
    } catch (UsernameNotFoundException var6) {
        if (authentication.getCredentials() != null) {
            String presentedPassword = authentication.getCredentials().toString();
            this.passwordEncoder.isPasswordValid(this.userNotFoundEncodedPassword, presentedPassword, (Object)null);
        }

        throw var6;
    } catch (Exception var7) {
        throw new InternalAuthenticationServiceException(var7.getMessage(), var7);
    }

    if (loadedUser == null) {
        throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
    } else {
        return loadedUser;
    }
}
复制代码

 

它还有一个重要的方法是
复制代码
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    Object salt = null;
    if (this.saltSource != null) {、
      //此方法在你的配置文件中去配置实现的  也是spring security加密的关键    ------划重点
        salt = this.saltSource.getSalt(userDetails);
    }

    if (authentication.getCredentials() == null) {
        this.logger.debug("Authentication failed: no credentials provided");
        throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
    } else {
        String presentedPassword = authentication.getCredentials().toString();
        if (!this.passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
            this.logger.debug("Authentication failed: password does not match stored value");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
    }
}
复制代码

 


这个方法的坑点仍是挺多的,主要的意思就是拿到经过用户姓名得到的该用户的信息(密码等)和用户输入的密码加密后对比,若是不正确就会报错Bad credentials的错误。
为何说这个方法坑,由于注意到
this.passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)
这里面他自带的一个方法用的是MD5的加密帮你加密在和你存入这个用户时的密码对比,
复制代码
public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
    String pass1 = encPass + "";
    String pass2 = this.mergePasswordAndSalt(rawPass, salt, false);
    if (this.ignorePasswordCase) {
        pass1 = pass1.toLowerCase(Locale.ENGLISH);
        pass2 = pass2.toLowerCase(Locale.ENGLISH);
    }

    return PasswordEncoderUtils.equals(pass1, pass2);
}
复制代码

 


能够注意到在生成pass2的时候传入了salt对象,这个salt对象能够经过配置文件去实现,也能够本身写一个实现类来完成。能够说是是和用户输入密码匹配的关键点所在。
6.UserDetails与UserDetailsService,这两个接口在上面都出现了,先看UserDetails是什么:
复制代码
package org.springframework.security.core.userdetails;

import java.io.Serializable;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}
复制代码

 


有没有发现它和前面的Authentication接口很像,好比它们都拥有username,authorities,区分他们也是本文的重点内容之一。
Authentication的getCredentials()与UserDetails中的getPassword()须要被区分对待,前者是用户提交的密码凭证,后者是用户正确的密码,
认证器其实就是对这二者的比对。Authentication中的getAuthorities()实际是由UserDetails的getAuthorities()传递而造成的。
还记得Authentication接口中的getUserDetails()方法吗?其中的UserDetails用户详细信息即是通过了AuthenticationProvider以后被填充的。
public interface UserDetailsService {
   UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

 

UserDetailsService和AuthenticationProvider二者的职责经常被人们搞混,关于他们的问题在文档的FAQ和issues中家常便饭。记住一点便可,敲黑板!!!UserDetailsService只负责从特定的地方(一般是数据库)加载用户信息,仅此而已,记住这一点,能够避免走不少弯路。UserDetailsService常见的实现类有JdbcDaoImpl,InMemoryUserDetailsManager,前者从数据库加载用户,后者从内存中加载用户,也能够本身实现UserDetailsService,一般这更加灵活。
 

ok,到此咱们能够来走一遍流程了。

首先咱们得有一个pojo对象,去实现UserDetail得接口,继承一下几个方法

复制代码
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    if(roles == null || roles.size()<=0){
        return null;
    }
    List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
    for(Role r:roles){
        authorities.add(new SimpleGrantedAuthority(r.getRoleValue()));
    }
    return authorities;
}

public String getPassword() {
    return password;
}

@Override
public String getUsername() {
    return email;
}

@Override
public boolean isAccountNonExpired() {
    return true;
}

@Override
public boolean isAccountNonLocked() {
    return true;
}

@Override
public boolean isCredentialsNonExpired() {
    return true;
}

@Override
public boolean isEnabled() {
    if(StringUtils.isNotBlank(state) && "1".equals(state) && StringUtils.isNotBlank(enable) && "1".equals(enable)){
        return true;
    }
    return false;
}

@Override
public boolean equals(Object obj) {
    if (obj instanceof User) {
        return getEmail().equals(((User)obj).getEmail())||getUsername().equals(((User)obj).getUsername());
    }
    return false;
}
@Override
public int hashCode() {
    return getUsername().hashCode();
}
复制代码

 

(1)其中 getAuthorities 方法是获取用户角色信息的方法,用于受权。不一样的角色能够拥有不一样的权限。css

(2)帐户未过时、帐户未锁定和密码未过时咱们这里没有用到,直接返回 True,你也能够根据本身的应用场景写本身的业务逻辑。html

(3)为了区分是不是同一个用户,重写 equals 和 hashCode 方法。java

由于实现接口以后能够得到数据库中的真是存在的信息;git

使用这个框架之间咱们要引入它,首先要在web.xml文件中引入它web

复制代码
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
复制代码

 



而后UsernamePasswordAuthenticationFilter这个过滤器会接受到此方法,在源码里面已经帮咱们实现得到密码以及用户名的操做,而且规定post请求方法
具体代码以下:
复制代码
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    if (this.postOnly && !request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    } else {
        String username = this.obtainUsername(request);
        String password = this.obtainPassword(request);
        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        this.setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}
复制代码

 


在现实生活中,开发中能够增长的逻辑不少,因此通常都会重写这个方法;咱们要建一个本身的类去继承这个类:
复制代码
public class AccountAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private String codeParameter = "code";

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String username = this.obtainUsername(request);
        String password = this.obtainPassword(request);
        String code = this.obtainCode(request);
        String caChecode = (String)request.getSession().getAttribute("VERCODE_KEY");
        boolean flag = CodeValidate.validateCode(code,caChecode);
        if(!flag){
            throw new UsernameNotFoundException("验证码错误");
        }
        if(username == null) {
            username = "";
        }
        if(password == null) {
            password = "";
        }
        username = username.trim();
        //经过构造方法实例化一个 UsernamePasswordAuthenticationToken 对象,此时调用的是 UsernamePasswordAuthenticationToken 的两个参数的构造函数
        //其中 super(null) 调用的是父类的构造方法,传入的是权限集合,由于目前尚未认证经过,因此不知道有什么权限信息,这里设置为 null,而后将用户名和密码分别赋值给
        // principal 和 credentials,一样由于此时还未进行身份认证,因此 setAuthenticated(false)。
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        //setDetails(request, authRequest) 是将当前的请求信息设置到 UsernamePasswordAuthenticationToken 中。
        this.setDetails(request, authRequest);
        //经过调用 getAuthenticationManager() 来获取 AuthenticationManager,经过调用它的 authenticate 方法来查找支持该
        // token(UsernamePasswordAuthenticationToken) 认证方式的 provider,而后调用该 provider 的 authenticate 方法进行认证)。
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    protected String obtainCode(HttpServletRequest request) {
        return request.getParameter(this.codeParameter);
    }
}
复制代码

 

里面咱们完成了一个验证码的验证工做,而且把仅为post请求给屏蔽,获取到用户名和用户密码后,咱们把它放在了UsernamePasswordAuthenticationToken类里,进去以后看到了
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
    super((Collection)null);
    this.principal = principal;
    this.credentials = credentials;
    this.setAuthenticated(false);
}

 

代码中给予了注释,而后setDetails将其存入UsernamePasswordAuthenticationToken之中,而后咱们经过getAuthenticationManager()
获取AuthenticationManager这个接口,在调用接口里的方法,咱们继续查找会发现AuthenticationManager这个类实现了这个接口的方法,
在方法中它又调用了AuthenticationProvide这个接口,那AuthenticationProvide这个接口的实现类是AbstractUserDetailsAuthenticationProvider
而且实现了authenticate方法,在这个方法里面引用了两个重要的方法additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);
user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);
那这两个方法在子类
DaoAuthenticationProvider中实现,两个方法上面都有代码,可是咱们再看一下其中重点的方法

复制代码
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    UserDetails loadedUser;
    try {
    //很关键
        loadedUser = this.getUserDetailsService().loadUserByUsername(username);
    } catch (UsernameNotFoundException var6) {
        if (authentication.getCredentials() != null) {
            String presentedPassword = authentication.getCredentials().toString();
            this.passwordEncoder.isPasswordValid(this.userNotFoundEncodedPassword, presentedPassword, (Object)null);
        }

        throw var6;
    } catch (Exception var7) {
        throw new InternalAuthenticationServiceException(var7.getMessage(), var7);
    }

    if (loadedUser == null) {
        throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
    } else {
        return loadedUser;
    }
}
复制代码

 

那个注释的地方是要得到一个UserDetails,上面有说到UserDetailsService常见的实现类有JdbcDaoImpl,InMemoryUserDetailsManager,为了简化咱们本身写一个实现类,
由于结合咱们pojo对象实现了UserDetails的接口,因此咱们建立以下类:
复制代码
public class AccountDetailsService implements UserDetailsService{
    @Autowired
    private UserService userService;
    @Autowired
    private RoleService roleService;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userService.findByEmail(username);
        if(user == null){
            throw new UsernameNotFoundException("用户名或密码错误");
        }
        List<Role> roles = roleService.findByUid(user.getId());
        user.setRoles(roles);
        return user;
    }
}
复制代码

 


实现了loadByUsername的方法。到此为止咱们咱们在逆向的回到了UsernamePasswordAuthenticationFilter上,且返回了一个Authentication对象。
咱们在第一个关键词SecurityContextHolder中将其取出,作一些本身的业务逻辑。



工做到此尚未结束,咱们还要去受权,对认证经过的人去受权,这里咱们能够xml去配置这些信息:咱们前面留了一个问题就是salt加密密码验证,咱们前面还不知道salt
对象是什么,因此须要配置一下
复制代码
<!-- 认证管理器,使用自定义的accountService,并对密码采用md5加密 -->
<security:authentication-manager alias="authenticationManager">
    <security:authentication-provider user-service-ref="accountService">
        <security:password-encoder hash="md5">
            <security:salt-source user-property="username"></security:salt-source>
        </security:password-encoder>
    </security:authentication-provider>
</security:authentication-manager>
复制代码

 


其实salt能够本身代码去配置,经过这个xml去配置也行,最紧要的仍是要和你原来数据库密码的加密方式有关系,我这里是用了pojo对象里的用户名做为salt对象,
因此个人密码加密方式就是username+password再用MD5加密了。那还有一个重要的工做就是受权配置

<security:http security="none" pattern="/css/**" />
<security:http security="none" pattern="/js/**" />
<security:http security="none" pattern="/images/**" />
<security:intercept-url pattern="/" access="permitAll"/>
<security:intercept-url pattern="/index**" access="permitAll"/>
<security:intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>

 

这些都是基础的一些受权操做,还有配置在咱们的AccountAuthenticationFilter类中是否是经过了验证
复制代码
<bean id="authenticationFilter" class="***.***.**.**.AccountAuthenticationFilter">
    <property name="filterProcessesUrl" value="/doLogin"></property>
    <property name="authenticationManager" ref="authenticationManager"></property>
    <property name="sessionAuthenticationStrategy" ref="sessionStrategy"></property>
    <property name="authenticationSuccessHandler">
        <bean class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
            <property name="defaultTargetUrl" value="/list"></property>
        </bean>
    </property>
    <property name="authenticationFailureHandler">
        <bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
            <property name="defaultFailureUrl" value="/login.jsp?error=fail"></property>
        </bean>
    </property>
</bean>
复制代码

 

其中defaultTargetUrl和defaultFailureUrl是经过和不经过的一些采起措施,一般是一些页面跳转。
其他的配置文件信息,我尚未琢磨透,之后有时间在发表一篇。


最后:用一张图大体的总结下它的具体流程(本图来自王林永老师的gitchat):

 https://www.cnblogs.com/zahfyy/p/9720124.htmlspring

相关文章
相关标签/搜索