Spring Boot + Security + JWT 實現Token驗證+多Provider——登陸系統

首先呢就是需求:web

一、帳號、密碼進行第一次登陸,得到token,以後的每次請求都在請求頭裏加上這個token就不用帶帳號、密碼或是session了。spring

二、用戶有兩種類型,具體表如今數據庫中存用戶信息時是分開兩張表進行存儲的。數據庫

爲何會分開存兩張表呢,這個設計的時候是先設計的表結構,有分開的必要因此就分開存了,也沒有想過以後Security 這塊須要進行一些修改,可是分開存就分開存吧,Security 這塊也不是很複雜。json

maven就是這兩:安全

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

 

而後直接說代碼吧,首先呢是實現dao層,這一層就不貼代碼了,反正就是能根據用戶名能返回用戶信息就行了。session

因此其實第一步是實現本身的安全模型:app

這一步是實現UserDetails這個接口,其中我額外添加了用戶類型、用戶Id。其餘的都是UserDetails接口必須實現的。maven

/**
 * 安全用戶模型
 * @author xuwang
 * Created on 2019/05/28 20:07
 */
public class XWUserDetails implements UserDetails {
    //用戶類型code
    public final static String USER_TYPE_CODE = "1";
    //管理員類型code
    public final static String MANAGER_TYPE_CODE = "2";
    //用戶id
    private Integer userId;
    //用戶名
    private String username;
    //密碼
    private String password;
    //用戶類型
    private String userType;
    //用戶角色表
    private Collection<? extends GrantedAuthority> authorities;

    public XWUserDetails(Integer userId,String username, String password, String userType, Collection<? extends GrantedAuthority> authorities){
        this.userId = userId;
        this.username = username;
        this.password = password;
        this.userType = userType;
        this.authorities = authorities;
    }

    /**
     * 獲取權限列表
     * @return Collection
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    /**
     * 獲取用戶Id
     * @return String
     */
    public Integer getUserId() {
        return userId;
    }

    /**
     * 獲取用戶類型
     * @return String
     */
    public String getUserType() {
        return userType;
    }

    /**
     * 獲取密碼
     * @return String
     */
    @Override
    public String getPassword() {
        return password;
    }

    /**
     * 獲取用戶名
     * @return String
     */
    @Override
    public String getUsername() {
        return username;
    }

    /**
     * 帳號是否未過時
     * @return boolean
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 帳號是否未鎖定
     * @return boolean
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 憑證是否未過時
     * @return boolean
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 帳號是否已啓用
     * @return boolean
     */
    @Override
    public boolean isEnabled() {
        return true;
    }

 

 第二步是實現兩個UserDetailsService由於要從兩張表裏進行查詢,因此我就實現了兩個UserDetailsServiceide

 這一步呢,注入了dao層的東西,從數據庫中查詢出用戶信息,構建XWUserDetails並返回。spring-boot

/**
 * Manager專用的UserDetailsService
 * @author xuwang
 * Created on 2019/06/01 15:58
 */
@Service("managerDetailsService")
public class ManagerDetailsServiceImpl  implements UserDetailsService {
    @Resource
    ScManagerMapper_Security scManagerMapper_security;
    @Resource
    ScRoleMapper_Security scRole_Mapper_security;

    /**
     *  根據用戶名從數據庫中獲取XWUserDetails
     * @param username
     * @return UserDetails
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //獲取用戶信息
        ScManager user = scManagerMapper_security.findByUsername(username);
        //獲取角色列表
        List<String> roles = scRole_Mapper_security.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
        } else {
            return new XWUserDetails(user.getId(),user.getManagerName(), user.getLoginPass(),XWUserDetails.MANAGER_TYPE_CODE, roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
        }
    }
}

 

/**
 * User專用的UserDetailsService
 * @author xuwang
 * Created on 2019/06/01 15:58
 */
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
    @Resource
    ScUserMapper_Security userMapper_security;
    @Resource
    ScRoleMapper_Security scRole_Mapper_security;
    /**
     *  根據用戶名從數據庫中獲取XWUserDetails
     * @param username
     * @return UserDetails
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //獲取用戶信息
        ScUser user = userMapper_security.findByUsername(username);
        //獲取角色列表
        List<String> roles = scRole_Mapper_security.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
        } else {
            return new XWUserDetails(user.getId(),user.getName(),user.getPassword(), XWUserDetails.MANAGER_TYPE_CODE, roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
        }
    }

}

 

 

 第三步,實現兩個UsernamePasswordAuthenticationToken

這一步的話,其實單看不知道爲何實現兩個類,可是註釋裏面我寫了,而後真正的爲何,總體流程,到最後說吧。

/**
 * manager專用的UsernamePasswordAuthenticationToken
 * AuthenticationManager會遍歷使用Provider的supports()方法,判斷AuthenticationToken是否是本身想要的
 * @author xuwang
 * Created on 2019/06/01 15:58
 */
public class ManagerAuthenticationToken extends UsernamePasswordAuthenticationToken {
    public ManagerAuthenticationToken(Object principal, Object credentials) {
        super(principal, credentials);
    }
}
/**
 * User專用的UsernamePasswordAuthenticationToken
 * AuthenticationManager會遍歷使用Provider的supports()方法,判斷AuthenticationToken是否是本身想要的
 * @author xuwang
 * Created on 2019/06/01 15:58
 */
public class UserAuthenticationToken extends UsernamePasswordAuthenticationToken {
    public UserAuthenticationToken(Object principal, Object credentials){
        super(principal,credentials);
    }
}

 

 第四步,實現兩個AuthenticationProvider

這個地方用到了上面的兩個類,重點是supports()方法,這個方法是用來校驗傳進來的UsernamePasswordAuthenticationToken的,反正就表明着這個ManagerAuthenticationProvider就只適用於ManagerAuthenticationToken,另外一個同理,具體也是最後說吧。

/**
 * Manager專用的AuthenticationProvider
 * 選擇實現DaoAuthenticationProvider是由於比較方便且能用
 * @author xuwang
 * Created on 2019/06/01 15:58
 */
public class ManagerAuthenticationProvider extends DaoAuthenticationProvider {
    /**
     * 初始化 將使用Manager專用的userDetailsService
     * @param encoder
     * @param userDetailsService
     */
    public ManagerAuthenticationProvider(PasswordEncoder encoder, UserDetailsService userDetailsService){
        setPasswordEncoder(encoder);
        setUserDetailsService(userDetailsService);
    }

    @Override
    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        super.setPasswordEncoder(passwordEncoder);
    }

    @Override
    public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
        super.setUserDetailsPasswordService(userDetailsPasswordService);
    }

    /**
     * 判斷只有傳入ManagerAuthenticationToken的時候才使用這個Provider
     * supports會在AuthenticationManager層被調用
     * @param authentication
     * @return
     */
    public boolean supports(Class<?> authentication) {
        return ManagerAuthenticationToken.class.isAssignableFrom(authentication);
    }
}
/**
 * 實現User專用的AuthenticationProvider
 * 選擇實現DaoAuthenticationProvider是由於比較方便且能用
 * @author xuwang
 * Created on 2019/06/01 15:58
 */
public class UserAuthenticationProvider extends DaoAuthenticationProvider {

    /**
     * 初始化 將使用User專用的userDetailsService
     * @param encoder
     * @param userDetailsService
     */
    public UserAuthenticationProvider(PasswordEncoder encoder, UserDetailsService userDetailsService){
        setPasswordEncoder(encoder);
        setUserDetailsService(userDetailsService);
    }

    @Override
    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        super.setPasswordEncoder(passwordEncoder);
    }

    @Override
    public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
        super.setUserDetailsPasswordService(userDetailsPasswordService);
    }

    /**
     * 判斷只有傳入UserAuthenticationToken的時候才使用這個Provider
     * supports會在AuthenticationManager層被調用
     * @param authentication
     * @return
     */
    public boolean supports(Class<?> authentication) {
        return UserAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

 

第五步就是繼承實現這個WebSecurityConfigurerAdapter

這一步呢,主要是將上面兩個AuthenticationProvider加入到AuthenticationManager中,並向Spring中注入這個AuthenticationManager供Service在校驗帳號密碼時使用。

同時還注入了一個PasswordEncoder,也是一樣供Service層使用,反正就是其餘地方能用就是了,就不用new了。

而後是configure方法,這個裏面,具體就是Security 的配置了,爲何怎麼寫我就不說了,反正我這裏實現了url的配置、Session的關閉、Filter的設置、設置驗證失敗權限不足自定義返回值。

其中Filter、和驗證失敗權限不足再看後面的代碼吧,我也會貼上的。

關於AuthenticationManager,就是先用加密工具、和以前實現的UserDetailsService 構造兩個DaoAuthenticationProvider,而後在configureGlobal()方法中添加這兩個DaoAuthenticationProvider,最後authenticationManagerBean()方法進行注入。

/**
 * Security 配置
 * @author xuwang
 * Created on 2019/06/01 15:58
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class XWSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    @Qualifier("userDetailsService")
    private UserDetailsService userDetailsService;
    @Autowired
    @Qualifier("managerDetailsService")
    private UserDetailsService managerDetailsService;
    @Resource
    private XWAuthenticationTokenFilter xwAuthenticationTokenFilter;
    @Resource
    private EntryPointUnauthorizedHandler entryPointUnauthorizedHandler;
    @Resource
    private RestAccessDeniedHandler restAccessDeniedHandler;

    /**
     * 注入UserAuthenticationProvider
     * @return
     */
    @Bean("UserAuthenticationProvider")
    DaoAuthenticationProvider daoUserAuthenticationProvider(){
        return new UserAuthenticationProvider(encoder(), userDetailsService);
    }


    /**
     * 注入ManagerAuthenticationProvider
     * @return
     */
    @Bean("ManagerAuthenticationProvider")
    DaoAuthenticationProvider daoMangerAuthenticationProvider(){
        return new ManagerAuthenticationProvider(encoder(), managerDetailsService);
    }

    /**
     * 向AuthenticationManager添加Provider
     * @return
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth){
        auth.authenticationProvider(daoUserAuthenticationProvider());
        auth.authenticationProvider(daoMangerAuthenticationProvider());
    }


    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder);
    }

    /**
     * 注入AuthenticationManager
     * @return
     */
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 注入PasswordEncoder
     * @return
     */
    @Bean
    public PasswordEncoder encoder() {
        PasswordEncoder encoder =
                PasswordEncoderFactories.createDelegatingPasswordEncoder();
        return encoder;
    }


    /**
     * 具體Security 配置
     * @return
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.
                csrf().disable().//默認開啓,這裏先顯式關閉csrf
                sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //Spring Security永遠不會建立HttpSession,它不會使用HttpSession來獲取SecurityContext
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() //任何用戶任意方法能夠訪問/**
                .antMatchers("/base/login").permitAll() //任何用戶能夠訪問/user/**
                .anyRequest().authenticated() //任何沒有匹配上的其餘的url請求,只須要用戶被驗證
                .and()
                .headers().cacheControl();
        http.addFilterBefore(xwAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        http.exceptionHandling().authenticationEntryPoint(entryPointUnauthorizedHandler).accessDeniedHandler(restAccessDeniedHandler);
    }


}

 

 而後是上面的兩個Handler

這個很簡單,就是實現AccessDeniedHandler和AuthenticationEntryPoint就是了。

/**
 * 身份驗證失敗自定401返回值
 *
 * @author xuwang
 * Created on 2019/05/29 16:10.
 */
@Component
public class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
        httpServletResponse.setStatus(401);
    }
}
/**
 * 權限不足自定403返回值
 *
 * @author xuwang
 * Created on 2019/05/29 16:10.
 */
@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
        httpServletResponse.setStatus(403);
    }
}

 

 再而後就是JWT這一塊了。

首先是一個Token的工具類,裏面有些什麼東西直接看註釋就行了。

/**
 * JWT工具類
 *
 * @author xuwang
 * Created on 2019/05/28 20:16.
 */
@Component
public class XWTokenUtil implements Serializable {

    /**
     * 密鑰
     */
    private final String secret = "11111111";

    /**
     * 從數據聲明生成令牌
     *
     * @param claims 數據聲明
     * @return 令牌
     */
    private String generateToken(Map<String, Object> claims) {
        //有效時間
        Date expirationDate = new Date(System.currentTimeMillis() + 2592000L * 1000);
        return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    /**
     * 從令牌中獲取數據聲明
     *
     * @param token 令牌
     * @return 數據聲明
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    /**
     * 生成令牌
     *
     * @param userDetails 用戶
     * @return 令牌
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>(2);
        claims.put("sub", userDetails.getUsername());
        claims.put("userId", ((XWUserDetails)userDetails).getUserId());
        claims.put("userType", ((XWUserDetails)userDetails).getUserType());
        claims.put("created", new Date());
        return generateToken(claims);
    }

    /**
     * 從令牌中獲取用戶名
     *
     * @param token 令牌
     * @return 用戶名
     */
    public String getUsernameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 從令牌中獲取用戶類型
     *
     * @param token 令牌
     * @return 用戶類型
     */
    public String getUserTypeFromToken(String token) {
        String userType;
        try {
            Claims claims = getClaimsFromToken(token);
            userType = (String) claims.get("userType");
        } catch (Exception e) {
            userType = null;
        }
        return userType;
    }

    /**
     * 從令牌中獲取用戶Id
     *
     * @param token 令牌
     * @return 用戶Id
     */
    public Integer getUserIdFromToken(String token) {
        Integer userId;
        try {
            Claims claims = getClaimsFromToken(token);
            userId = (Integer) claims.get("userId");
        } catch (Exception e) {
            userId = null;
        }
        return userId;
    }

    /**
     * 判斷令牌是否過時
     *
     * @param token 令牌
     * @return 是否過時
     */
    public Boolean isTokenExpired(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 刷新令牌
     *
     * @param token 原令牌
     * @return 新令牌
     */
    public String refreshToken(String token) {
        String refreshedToken;
        try {
            Claims claims = getClaimsFromToken(token);
            claims.put("created", new Date());
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * 驗證令牌
     *
     * @param token       令牌
     * @param userDetails 用戶
     * @return 是否有效
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        XWUserDetails user = (xwUserDetails) userDetails;
        String username = getUsernameFromToken(token);
        return (username.equals(user.getUsername()) && !isTokenExpired(token));
    }
}

 

 而後是Filter

這個Filter你們都知道請求發過來,會先進行這個Filter裏面的方法,這裏的邏輯也很簡單,從Token中拿到身份信息,並進行驗證,驗證這裏我寫得比簡單,能夠再加邏輯。

UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

SecurityContextHolder.getContext().setAuthentication(authentication);

重點是這三行,這三行是什麼意思呢,前面說了需求是第一次登陸驗證成功,之後發請求使用Token就行了,這三行以前的邏輯是在校驗Token,從Token中獲取用戶信息,但系統中進行權限管理的是Spring Security,並無使用Spring Security 進行驗證啊,

因此須要作的就是這三行,這三行中的SecurityContextHolder就是:SecurityContextHolder是用來保存SecurityContext的。SecurityContext中含有當前正在訪問系統的用戶的詳細信息,

實際就是使用用戶信息構建authentication放到SecurityContextHolder就等於用戶已經登陸了,就不用再校驗密碼什麼的了。

/**
 * JWT Filter
 *
 * @author xuwang
 * Created on 2019/05/29 16:10.
 */
@Component
public class XWAuthenticationTokenFilter extends OncePerRequestFilter {

    @Resource
    ManagerDetailsServiceImpl managerDetailsService;
    @Resource
    UserDetailsServiceImpl userDetailsService;
    @Resource
    private XWTokenUtil xwTokenUtil;


    /**
     * 獲取驗證token中的身份信息
     * @author xuwang
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        //從請求頭中獲取token
        String authHeader = request.getHeader("Authorization");
        //token前綴
        String tokenHead = "Bearer ";
        if (authHeader != null && authHeader.startsWith(tokenHead)) {
            //去掉token前綴
            String authToken = authHeader.substring(tokenHead.length());
            //從token中獲取用戶名
            String username = XWTokenUtil.getUsernameFromToken(authToken);
            String userType = XWTokenUtil.getUserTypeFromToken(authToken);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = null;
                //根據從token中獲取用戶名從數據庫中獲取一個userDetails
                if(userType.equals(XWUserDetails.USER_TYPE_CODE)){
                    //普通用戶
                    userDetails = userDetailsService.loadUserByUsername(username);
                }else if(userType.equals(XWUserDetails.MANAGER_TYPE_CODE)){
                    //管理員
                    userDetails = managerDetailsService.loadUserByUsername(username);
                }
                if (xwTokenUtil.validateToken(authToken, userDetails)) {
                    //token中的用戶信息和數據庫中的用戶信息對比成功後將用戶信息加入SecurityContextHolder至關於登錄
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(request, response);
    }

}

 

而後是用戶登陸的接口了,我直接貼Service層的代碼吧

/**
 * @ClassName: loginServiceImpl
 * @ClassNameExplain:
 * @Description: 業務層實現類
 * @author xuwang
 * @date 2019-05-31 16:15:46
 */
@Service
public class LoginServiceImpl implements ILoginService {

    static final Logger logger = LoggerFactory.getLogger(LoginServiceImpl.class);

    @Resource
    private AuthenticationManager authenticationManager;
    @Autowired
    @Qualifier("userDetailsService")
    private UserDetailsService userDetailsService;
    @Autowired
    @Qualifier("managerDetailsService")
    private UserDetailsService managerDetailsService;
    @Resource
    private XWTokenUtil xwTokenUtil;

    @Override
    public LoginVO login(LoginIO loginIO) throws Exception {
        //不一樣的用戶類型使用不一樣的登錄方式
        String token = "";
        UserDetails userDetails = null;
        if(loginIO.getType().equals(XWUserDetails.USER_TYPE_CODE)){
            //登陸
            login(new UserAuthenticationToken(loginIO.getUserName(), loginIO.getPassword()));
            userDetails = userDetailsService.loadUserByUsername(loginIO.getUserName());
            token = xwTokenUtil.generateToken(userDetails);
            logger.info("user[{}]登錄成功",loginIO.getUserName());
        }else if(loginIO.getType().equals(XWUserDetails.MANAGER_TYPE_CODE)){
            login(new ManagerAuthenticationToken(loginIO.getUserName(), loginIO.getPassword()));
            userDetails = managerDetailsService.loadUserByUsername(loginIO.getUserName());
            token = xwUtil.generateToken(userDetails);
            logger.info("manager[{}]登錄成功",loginIO.getUserName());
        }else {
            logger.error("type[{}]參數錯誤",loginIO.getType());
            //type參數錯誤
            throw new BusinessException(ExceptionConstants.PARAM_INVALID_CODE,
                    ExceptionConstants.PARAM_INVALID_MSG);
        }
        LoginVO loginVO = new LoginVO();
        loginVO.setToken(token);
        loginVO.setUserId(((XWUserDetails)userDetails).getUserId());
        return loginVO == null ? new LoginVO() : loginVO;
    }

    /**
     * 校驗帳號密碼並進行登錄
     * @param upToken
     */
    private void login(UsernamePasswordAuthenticationToken upToken){
        //驗證
        Authentication authentication = authenticationManager.authenticate(upToken);
        //將用戶信息保存到SecurityContextHolder=登錄
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }


}

 這個Service解釋一下就是:

loginIO能接收到用戶信息:帳號UserName、密碼Password、類型Type之類的,而後使用AuthenticationManager 進行校驗,再使用SecurityContextHolder進行登陸操做(上面解釋過了),最後返回Token(xwTokenUtil工具類生成的)和用戶Id。

最後解釋一下其中的流程,已經我爲何這麼去實現吧。

從Service中咱們能夠看到,登陸時使用的是先使用帳號密碼構建了一個UsernamePasswordAuthenticationToken,我這裏構建的是UserAuthenticationToken、ManagerAuthenticationToken,不過影響不大,都是它的子類,AuthenticationManager的authenticate將接受一個UsernamePasswordAuthenticationToken來進行驗證,最後才登陸。

上面的至關於Security的登陸使用流程。

而後解釋一下前面的那些全部的疑惑,在Service中使用AuthenticationManager的authenticate()方法進行校驗的時候,其實是會把UsernamePasswordAuthenticationToken傳遞給Provider進行校驗的,Provider裏呢又讓Service去校驗的。這是類和類之間的關係,而後還有實際代碼關係是,AuthenticationManager中會有一個Provider列表,進行校驗的時候會遍歷使用每個Provider的supports()方法,這個supports()方法將校驗傳進來的UsernamePasswordAuthenticationToken是本身想要的UsernamePasswordAuthenticationToken嗎,若是是的話就使用這個Provider進行校驗。因此我實現了UserAuthenticationToken、ManagerAuthenticationToken,還實現了ManagerAuthenticationProvider、UserAuthenticationProvider以及其中的supports()方法,這樣authenticationManager.authenticate(new UserAuthenticationToken)就會使用UserAuthenticationProvider中的UserDetailsServiceImpl去校驗了。ManagerAuthenticationToken同理。

上面的我本身是以爲寫得是比較清晰了。若是實在是看不明白,或者實際上是我仍是寫得太爛了,能夠本身跟一下代碼,就從AuthenticationManager.authenticate()方法跟進去就行了,

具體跟代碼的時候要注意,AuthenticationManager的默認實現是ProviderManager,因此其實看到的是ProviderManager的authenticate()方法

圖中:

  1. 獲取到Authentication的類信息

  2. 獲得Provider列表的迭代器

  3.進行遍歷

  4.調用Provider的supports()方法

因此我重寫了兩個provider和其中supports()方法,和兩個AuthenticationToken。

 而後其實這個東西並不算是太複雜,本身去用和學習的時候,最好仍是先實現,而後在慢慢跟代碼,去猜去思考其中的流程,就行了。

最後是爲何要這樣去寫代碼、去注入、用這個方式進行加密、以及token中存放的信息、loginIo得設置等等的,這些都是能夠任意更改的,無需糾結太多,根據根據我的習慣和當時的業務改就行了,至於到底怎樣纔是最好的,我也沒太認真的去思考過,畢竟加班的時候只能先實現功能了,至於爲何在寫這個博客的時候還不去思考的緣由就是。。。由於這些並非重點

相關文章
相關標籤/搜索