Front and rear ends Separation Project - based user authentication SpringSecurity OAuth2.0

1 Introduction

  Now a lot of projects are based on APP mobile side and front and rear end of the separation project, based around before the end of the Session of the project has been put together out of favor and slowly fade out of our sight, especially when based on SpringCloud micro-service architecture and Vue, React single-page application after the popular, the situation is even worse. To this end has also been a focus of attention on the front and rear end of the separation of user authentication project, different from the past based on Session user authentication, user authentication is based on Token mainstream options (as to what is the Token authentication, relevant information online, we you can see), and Java-based authentication framework has two Apache Shiro and SpringSecurity, I will not discuss in this one is better, we can look at their own Baidu, the main discussion paper is based on SpringSecurity user authentication.

2. Preparation

  Creating a three Clause project awbeci-ssb project contains two main sub awbeci-ssb-api and awbeci-ssb-core, and related to the introduction SpringSecurity jar package, as follows:

Here is my project directory structure, code I would put out in the last

 

 

 

 

 

 

 

 

 

 

3, configuration SpringSecurity OAuth2.0 resource services and certification services

1) What is the resource service?

  Resource service is normally configured user name and password or phone number verification code, social login, and so configure user authentication as well as some static files and address related requests to the address set up certification, and so do the role.

2) What is a certified service?

  Certification is used to configure authentication methods, such as Redis, JWT, etc., there is a set ClientId and ClientSecret, only the right ClientId and ClientSecret to get Token.

3) First, we create two classes

  AuthorizationServerConfigurerAdapter a succession of SsbAuthorizationServerConfig as an authentication service class and a succession of ResourceServerConfigurerAdapter

SsbResourceServerConfig resource service class, two classes realize, probably already 50% complete, the code is as follows:

@Configuration
@EnableAuthorizationServer
public class SsbAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    public SsbAuthorizationServerConfig(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.userDetailsService(userDetailsService);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory () // configuration memory, the database may be 
                .withClient ( "awbeci") // ClientID 
                .secret ( "awbeci-Secret") 
                .accessTokenValiditySeconds (3600) // token seconds effective time 
                .authorizedGrantTypes ( "refresh_token "," password "," authorization_code ") // token mode 
                .scopes (" all ") // restrictions allow permissions configuration 
                .and () // configure the following second application (do not know is how dynamic configuration, it can not use the memory mode, you should use the database schema Come) 
                .withClient ( "the Test") 
                .scopes ( "testSc") 
                .accessTokenValiditySeconds (7200) 
                .scopes ( "All");
    }
}
@Configuration @EnableResourceServer class SsbResourceServerConfig the extends ResourceServerConfigurerAdapter {public @Autowired protected AuthenticationSuccessHandler ssbAuthenticationSuccessHandler; //.loginPage("/authentication/login ") // login to submit action, app will use // user name sign-in address @Autowired AuthenticationFailureHandler ssbAuthenticationFailureHandler protected; @Autowired Private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig; @Override public void the configure (HttpSecurity HTTP) throws Exception { // so in our app login action as long as we submit, do not jump to the login page      http.formLogin () / / login page, app less than .loginProcessingUrl ( "/ form / token") // returns successfully processor Token .successHandler (ssbAuthenticationSuccessHandler) // failed processor .failureHandler (ssbAuthenticationFailureHandler);       HTTP // phone verification code .AND () .authorizeRequests () .antMatchers ( .apply(smsCodeAuthenticationSecurityConfig) .and() .authorizeRequests() //手机验证码登录地址 .antMatchers("/mobile/token", "/email/token") .permitAll() "/register", "/social/**", "/**/*.js", "/**/*.css", "/**/*.jpg", "/**/*.png", "/**/*.woff2", "/code/image") .permitAll () // above requests without authentication .anyRequest () .authenticated() .and() .csrf().disable(); } }

4, username and password to log acquisition Token

  Once configured, Now I can officially start using SpringSecurity OAuth configure the user name and password, which is the form login, SpringSecurity default login and Basic Login Form there, we have  

  configure method SsbResourceServerConfig class above set http.formLogin () is a form login, which is the user name password here, the default SpringSecurity has achieved a package form log, so we just set the Token returned after success well, we have created a succession of SsbAuthenticationSuccessHandler SavedRequestAwareAuthenticationSuccessHandler class code is as follows:

@Component("ssbAuthenticationSuccessHandler")
public class SsbAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private AuthorizationServerTokenServices authorizationServerTokenServices;
    /*
     * (non-Javadoc)
     *
     * @see org.springframework.security.web.authentication.
     * AuthenticationSuccessHandler#onAuthenticationSuccess(javax.servlet.http.
     * HttpServletRequest, javax.servlet.http.HttpServletResponse,
     * org.springframework.security.core.Authentication)
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        String header = request.getHeader("Authorization");
        String name = authentication.getName();
        String password = (String) authentication.getCredentials();
        if (header == null || !header.startsWith("Basic ")) {
            throw new UnapprovedClientAuthenticationException("请求头中无client信息");
        }

        String[] tokens = extractAndDecodeHeader(header, request);
        assert tokens.length == 2;
        String clientId = tokens[0];
        String clientSecret = tokens[1];

        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);

        if (clientDetails == null) {
            throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在:" + clientId);
        } else if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) {
            throw new UnapprovedClientAuthenticationException("clientSecret不匹配:" + clientId);
        }

        TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "custom");

        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);

        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);

        OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(token));
    }

    private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException {

        byte[] base64Token = header.substring(6).getBytes("UTF-8");
        byte[] decoded;
        try {
            decoded = Base64.decode(base64Token);
        } catch (IllegalArgumentException e) {
            throw new BadCredentialsException("Failed to decode basic authentication token");
        }
        String token = new String(decoded, "UTF-8");
        int delim = token.indexOf(":");
        if (delim == -1) {
            throw new BadCredentialsException("Invalid basic authentication token");
        }
        return new String[] { token.substring(0, delim), token.substring(delim + 1) };
    }
}

  This can successfully Token returned to the front end, and then we have to release / form / token request address, we have the release of the configure method SsbResourceServerConfig class, and class successfully processed provided

ssbAuthenticationSuccessHandler method, and failure treatment class ssbAuthenticationFailureHandler follows:

  Here we look at PostMan test with success, but before that we have to create a class based UserDetailsService of ApiUserDetailsService, the use of this class is to query the user authentication information from the database, here we have no query from the database, but you know what to do with this class, the code is as follows:

@Component
public class ApiUserDetailsService implements UserDetailsService{

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private PasswordEncoder passwordEncoder;

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.security.core.userdetails.UserDetailsService#
     * loadUserByUsername(java.lang.String)
     */
    // 这里的username 可以是username、mobile、email
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        logger.info("表单登录用户名:" + username);
        return buildUser(username);
    }

    SocialUser buildUser Private (String userId) { 
        // find user information based on the user name 
        // according to the obtained user information to determine whether the user is frozen 
        String password = passwordEncoder.encode ( "123456"); 
        logger.info ( "database password is: "+ password); 
        return new new SocialUser (the userId, password, 
                to true, to true, to true, to true, 
                AuthorityUtils.commaSeparatedStringToAuthorityList (" ADMIN, users with the ROLE_USER ")); 
    } 
}

  

 

 

   

 

 

   

Such login user name password will be successful! Let's deal with the phone number verification code to obtain token.

5, phone number verification code to obtain Token

    To configure redis First, we put this code into redis inside (note that this code is actually sent to redis which holds a record, which I will not detail), configuration is as follows:

= 127.0.0.1 spring.redis.host 
spring.redis.password = zhangwei 
spring.redis.port = 6379 
# connection time (ms) 
spring.redis.timeout = 30000

Once set up, we want to create four classes

1. Based on the SmsCodeAuthenticationToken AbstractAuthenticationToken class, the user information stored token class 

2 SmsCodeAuthenticationFilter based AbstractAuthenticationProcessingFilter the class, which is a filter, parameters such as the phone number of the request, obtain the codes, and configured the Authentication 

3. AuthenticationProvider of classes based SmsCodeAuthenticationProvider, this class is to verify your phone number and verification code are correct, and return to the Authentication 

4. based SecurityConfigurerAdapter of SmsCodeAuthenticationSecurityConfig class that is the nexus of use, the top three class configuration to there and put it inside from the use of resources and services

Let's resolve one by one these four categories.

 

 

 

 

(1), SmsCodeAuthenticationToken class code is as follows:

// user's information storage class 
public class SmsCodeAuthenticationToken the extends AbstractAuthenticationToken { 

    // on the inside of all the user information, such as user name, phone number, password, etc. 
    Private Final Object Principal; 
    // here stored certificate information, such as passwords, codes other 
    private Object credentials; 

    before // configured unauthenticated user information 
    SmsCodeAuthenticationToken (Principal Object, Object Credentials) { 
        Super (null); 
        this.principal = Principal; 
        this.credentials = Credentials; 
        this.setAuthenticated (to false); 
    } 

    // Construct authenticated user information 
    SmsCodeAuthenticationToken (Principal Object,  
                               Object Credentials,
                               Collection Authorities <the extends the GrantedAuthority?>) { 
        Super (Authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true); // must use super, as we override
    }

    public Object getCredentials() {
        return this.credentials;
    }

    public Object getPrincipal() {
        return this.principal;
    }

    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        }
        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
    }
}

(2), SmsCodeAuthenticationFilter class, as follows

// SMS codes interceptor 
public class SmsCodeAuthenticationFilter the extends AbstractAuthenticationProcessingFilter { 

    Private postOnly = Boolean to true; 

    // phone number parameter variables 
    Private mobileParameter String = "Mobile"; 
    Private smsCode String = "smsCode"; 

    SmsCodeAuthenticationFilter () { 
        Super (new new AntPathRequestMatcher ( "/ Mobile / token", "the POST")); 
    } 

    / ** 
     * add unauthenticated user authentication information, and then inside an official authentication provider 
     * 
     * @param HttpServletRequest 
     * @param HttpServletResponse 
     * @return 
     * @throws of AuthenticationException 
     * @ IOException throws 
     * @throws ServletException 
     * /
    public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
            throws AuthenticationException, IOException, ServletException {
        if (postOnly && !httpServletRequest.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + httpServletRequest.getMethod());
        }

        String mobile = obtainMobile(httpServletRequest);
        String smsCode = obtainSmsCode(httpServletRequest);
        
    //todo:验证短信验证码2 if (mobile == null) { mobile = ""; } mobile = mobile.trim(); SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile, smsCode); // Allow subclasses to set the "details" property setDetails(httpServletRequest, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } /** * 获取手机号 */ private String obtainMobile(HttpServletRequest request) { return request.getParameter(mobileParameter); } private String obtainSmsCode(HttpServletRequest request) { return request.getParameter(smsCode); } private void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) { authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); } public void setMobileParameter(String usernameParameter) { Assert.hasText(usernameParameter, "Username parameter must not be empty or null"); this.mobileParameter = usernameParameter; } public void setPostOnly(boolean postOnly) { this.postOnly = postOnly; } public final String getMobileParameter() { return mobileParameter; } }

(3), SmsCodeAuthenticationProvider class, as follows

// class where user authentication 
public class SmsCodeAuthenticationProvider the implements the AuthenticationProvider { 

   Private RedisTemplate <Object, Object> redisTemplate; 

    // note userdetailservice here, because no @Component SmsCodeAuthenticationProvider class 
    // so there can not add @Autowire, only by setting the job outside 
    the userDetailsService userDetailsService Private; 

    @Bean 
    public PasswordEncoder the PasswordEncoder () { 
        return new new BCryptPasswordEncoder (); 
    } 

    / ** 
     * where the user authentication information 
     * @param authentication 
     * @return 
     * @throws of AuthenticationException 
     * / 
    public the authenticate the authentication (the authentication authentication) throws of AuthenticationException {
        AuthenticationToken = SmsCodeAuthenticationToken (SmsCodeAuthenticationToken) authentication; 
// Mobile = String (String) authenticationToken.getPrincipal (); 
        String Mobile authentication.getName = (); 
        String smsCode = (String) authenticationToken.getCredentials (); 

        // Get the redis from the phone number verification code 
        String smsCodeFromRedis = (String) redisTemplate.opsForValue () GET (Mobile);. 
        (! smsCode.equals (smsCodeFromRedis)) {IF 
            the throw new new InternalAuthenticationServiceException ( "phone code is incorrect"); 
        } 

        the UserDetails User = userDetailsService.loadUserByUsername (Mobile); 
        IF (User == null) { 
            the throw new new InternalAuthenticationServiceException ( "unable to get user information") ; 
        } 

        SmsCodeAuthenticationToken authenticationResult = new new SmsCodeAuthenticationToken (User, null, user.getAuthorities ());
        authenticationResult.setDetails(authenticationToken.getDetails());
        return authenticationResult;
    }

    public boolean supports(Class<?> authentication) {
        return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
    }

    public UserDetailsService getUserDetailsService() {
        return userDetailsService;
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    public RedisTemplate<Object, Object> getRedisTemplate() {
        return redisTemplate;
    }

    public void setRedisTemplate(RedisTemplate<Object, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
}

  

(4), SmsCodeAuthenticationSecurityConfig class, as follows

@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    @Autowired
    private AuthenticationSuccessHandler ssbAuthenticationSuccessHandler;

    @Autowired
    private AuthenticationFailureHandler ssbAuthenticationFailureHandler;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Override
    public void configure(HttpSecurity http) throws Exception {

        SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
        smsCodeAuthenticationFilter.setAuthenticationManager (http.getSharedObject (AuthenticationManager.class)); 
        smsCodeAuthenticationFilter.setAuthenticationSuccessHandler (ssbAuthenticationSuccessHandler) 
        smsCodeAuthenticationFilter.setAuthenticationFailureHandler (ssbAuthenticationFailureHandler) 

        SmsCodeAuthenticationProvider smsCodeDaoAuthenticationProvider SmsCodeAuthenticationProvider = new (); 
        smsCodeDaoAuthenticationProvider.setUserDetailsService (userDetailsService) 
        smsCodeDaoAuthenticationProvider.setRedisTemplate (redisTemplate) 
        http.authenticationProvider (smsCodeDaoAuthenticationProvider)
                .addFilterAfter (smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) 
    } 
}

I do not have the above code detailed notes say, well let's see if the next test successful:

Well, the phone number verification code user authentication are successful!

6, E-mail verification code to obtain Token

E-mail verification code and phone number above code to sign similar, try to write your own look.

7, the token stored inside the Redis

This is to expand the capabilities of the students do not need to be ignored.

We transform what SsbAuthorizationServerConfig class to support Redis save the token, as follows

@Autowired
private TokenStore redisTokenStore;

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        //使用Redis作为Token的存储
        endpoints
                .tokenStore(redisTokenStore)
                .userDetailsService(userDetailsService);
}

  

Then create it RedisTokenStoreConfig class

@Configuration
@ConditionalOnProperty(prefix = "ssb.security.oauth2", name = "storeType", havingValue = "redis")
public class RedisTokenStoreConfig {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    public TokenStore redisTokenStore(){
        return new RedisTokenStore(redisConnectionFactory);
    }

}

  

Add application.properties inside

ssb.security.oauth2.storeType=redis

Well, we next tested

 

 

 

 

This successfully saved to the redis.

8, using the Token generated JWT

jwt What is your own Baidu.

Above all, to transform SsbAuthorizationServerConfig class code is as follows:

@Autowired (= required to false) 
Private JwtAccessTokenConverter jwtAccessTokenConverter; 

@Autowired (= required to false) 
Private TokenEnhancer jwtTokenEnhancer; 

@Override 
public void Configure (AuthorizationServerEndpointsConfigurer Endpoints) throws Exception { 

        // Redis used as a storage Token 
        Endpoints 
// .tokenStore (redisTokenStore) 
.authenticationManager // (the authenticationManager) 
                .userDetailsService (userDetailsService); 

        //. 1, is provided in the form of token to jwt 
        // 2, authentication information provided jwt expand 
        if {(jwtAccessTokenConverter = null && jwtTokenEnhancer = null!!)
            TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
            List<TokenEnhancer> enhancers = new ArrayList<TokenEnhancer>();
            enhancers.add(jwtTokenEnhancer);
            enhancers.add(jwtAccessTokenConverter);
            enhancerChain.setTokenEnhancers(enhancers);
            endpoints.tokenEnhancer(enhancerChain)
                    .accessTokenConverter(jwtAccessTokenConverter);
        }
    }

  

Then let us create JwtTokenStoreConfig class code is as follows:

@Configuration
@ConditionalOnProperty(
        prefix = "ssb.security.oauth2",
        name = "storeType",
        havingValue = "jwt",
        matchIfMissing = true)
public class JwtTokenStoreConfig {

    @Value("${ssb.security.jwt.signingKey}")
    private String signingkey;

    @Bean
    public TokenEnhancer jwtTokenEnhancer() {
        return new SsbJwtTokenEnhancer();
    }

    @Bean
    public TokenStore jetTokenStroe() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        = New new JwtAccessTokenConverter jwtAccessTokenConverter JwtAccessTokenConverter (); 
        // Set default value 
        IF (StringUtils.isEmpty (signingkey)) { 
            signingkey = "awbeci"; 
        } 
        // key into the profile 
        jwtAccessTokenConverter.setSigningKey (signingkey); 
        return jwtAccessTokenConverter; 
    } 
}

Then create a class based JwtTokenEnhancerHandler ApiJwtTokenEnhancerHandler the following code:

/**
 * 拓展jwt token里面的信息
 */
@Service
public class ApiJwtTokenEnhancerHandler implements JwtTokenEnhancerHandler {

    public HashMap<String, Object> getInfoToToken() {
        HashMap<String, Object> info = new HashMap<String, Object>();
        info.put("author", "张威");
        info.put("company", "awbeci-copy");
        return info;
    }
}

  

Finally, do not forget to set inside look at application.properties

ssb.security.oauth2.storeType=jwt
ssb.security.jwt.signingKey=awbeci

Well, we have to test it

 

 

 

 

9, summary

1) spring-security package has helped us form a user name and password to login, and as long as we realize the phone number verification code to sign in like
2) a total of six classes, a resource service class ResourceServerConfigurer, a certification service class AuthorizationServerConfigurer, a phone verification Token class code, a phone verification code Filter class, a certified phone code Provider class, a class configure class configuration, so much, in fact, not difficult, and sometimes write a lot of people look online, looking to be scared to death.

Guess you like

Origin www.cnblogs.com/liboware/p/12535100.html