主要提供两种示例的认证方式,之后如果还有其它需要认证的方式可以效仿
1、认证构成
主要把认证代码分成4部分:
1、token携带需要认证的信息
2、provider提供认证服务
3、config配置需要提供的认证服务
4、业务代码中的认证
2、第一种认证方式DaoAuthenticationProvider
用户名密码认证,这是由spring security提供的最常用的认证方式,我们主要只需要关注三个类UserDetails用户信息实体类,UserDetailService自定义实现类获取用户信息,PasswordEncoder根据用户密码加载用户信息的加密方法。
1、config配置
import org.springframework.context.annotation.Bean; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import com.xyqq.app.user.impl.UserDetailsServiceImpl; @EnableWebSecurity @EnableWebMvc public class WebMvcConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean @Override public UserDetailsService userDetailsService() { return new UserDetailsServiceImpl(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { DaoAuthenticationProvider daoAuthProvider = new DaoAuthenticationProvider(); daoAuthProvider.setUserDetailsService(this.userDetailsService()); daoAuthProvider.setPasswordEncoder(this.passwordEncoder()); auth.authenticationProvider(daoAuthProvider); } }
继承WebSecurityConfigurerAdapter 类,重写config方法,摄者AuthenticationManagerBuilder,童工认证服务。重写DaoAuthenticationProvider 中的userDetailService方法,每个系统都有自己不同字段用户名去获取用户信息,所以这时候需要重写获取用户信息的方法。重写PsswordEncoder,系统对于用户密码的加密也是不同的,所以重写密码的加密解密方式。
2、业务层认证
private void login(String username, String password) throws BusinessException { try { LOGGER.info("Start to login {} ...", username); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password); Authentication authentication = this.authenticationManager.authenticate(token); SecurityContextHolder.getContext().setAuthentication(authentication); LOGGER.info("User {} login successfully", this.getLoginUserFromSession()); } catch (Exception ex) { LOGGER.error("User {} login failed", username, ex); throw new BusinessException(ErrorCodes.Login_Failed); } }
因为DaoAuthenticationProvider是由Spring Security提供的,所以我们不需要在写provider和token,当进行authenticationManager认证完之后会返回一个Authentication对象,我们使用SecurityContextHolder.getContext().serAuthentication()方法即可认证完毕。
3、第二种认证方式,微信小程序openId
1、config配置
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import com.xyqq.app.security.WeChatAuthenticationProvider; import com.xyqq.app.user.UserService; @EnableWebSecurity @EnableWebMvc public class WebMvcConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer { @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Autowired private UserService userService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(new WeChatAuthenticationProvider(userService)); } }
继承WebSecurityConfigurerAdaptor,重新实现configure方法,向AuthenticationMangerBuider中注册信息的认证服务。
2、创建认证token类
import java.util.ArrayList; import java.util.Collection; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; public class WeChatAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = -3625665688626567368L; private Collection<GrantedAuthority> authorities = new ArrayList<>(); private Object principal; private Object credentials; private String openId; public WeChatAuthenticationToken(String openId) { super(null); this.openId = openId; } public void setPrincipal(Object principal) { this.principal = principal; } @Override public Object getPrincipal() { return this.principal; } @Override public Object getCredentials() { return this.credentials; } @Override public Collection<GrantedAuthority> getAuthorities() { return this.authorities; } public void setAuthorities(Collection<? extends GrantedAuthority> authorities) { this.authorities.addAll(authorities); } public String getOpenId() { return openId; } }
这个认证服务是根据用户微信小程序的openId来进行认证的,所以我们需要继承AbstractAuthenticationTaoken然后重新提供我们需要认证的变量openId。
3、创建认证服务provider
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import com.xyqq.app.user.UserInfo; import com.xyqq.app.user.UserService; import com.xyqq.app.user.domains.User; public class WeChatAuthenticationProvider implements AuthenticationProvider { private static final Logger LOGGER = LoggerFactory.getLogger(WeChatAuthenticationProvider.class); private UserService userService; public WeChatAuthenticationProvider(UserService userService) { this.userService = userService; } @Override public boolean supports(Class<?> authentication) { return authentication.equals(WeChatAuthenticationToken.class); } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { WeChatAuthenticationToken token = (WeChatAuthenticationToken) authentication; String openId = token.getOpenId(); LOGGER.info("Start to authenticate {} ...", openId); UserDetails loadedUser = null; try { User user = this.userService.getByOpenId(openId); loadedUser = new UserInfo(user); } catch (UsernameNotFoundException notFound) { throw notFound; } catch (Exception repositoryProblem) { throw new InternalAuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem); } token.setPrincipal(loadedUser); token.setDetails(loadedUser); token.setAuthorities(loadedUser.getAuthorities()); authentication.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(authentication); return authentication; } }
实现AuthenticationProvider接口,里面需要重写两个方法:supports(Class<?> authentication)和 authenticate(Authentication authentication),其中supports是指定这个provider所需要支持的认证token类,也就是我们上面创建的token类。第二个方法是进行用户的信息认证,使用系统的userService根据openId获取用户信息并进行认证。
4、业务层认证
WeChatAuthenticationToken token = new WeChatAuthenticationToken(wechatSession.getOpenid()); this.authenticationManager.authenticate(token);
4、附加
在继承了WebSecurityConfigurerAdapter中,去重写configure(AuthenticationManagerBuilder auth)方法,去个AuthenticationManagerBuilder设置认证服务,里面可以设置多个provider,认证的时候会根据顺序执行下去,并找到对应的认证服务
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(new WeChatAuthenticationProvider(userService)); auth.authenticationProvider(new UserAuthenticationProvider()); auth.authenticationProvider(new TokenAccessAuthenticationProvider(tokenService)); DaoAuthenticationProvider daoAuthProvider = new DaoAuthenticationProvider(); daoAuthProvider.setUserDetailsService(this.userDetailsService()); daoAuthProvider.setPasswordEncoder(this.passwordEncoder()); auth.authenticationProvider(daoAuthProvider); }