笔记
引入起始依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
UserDetails
UserDetails对象是用来存储在Spring Security上下文中,代表当前登录用户信息的。实现此步,只要自定义一个UserDetails类,继承Spring Security提供的User类:
org.springframework.security.core.userdetails.User
示例:
public class UserLoginDetails extends User{
private static final long serialVersionUID = -360606014621189433L;
private MUserDTO mUser;
//mUser为当前用户信息,authorities为当前用户的角色集合
public UserLoginDetails(MUserDTO mUser,
Collection<? extends GrantedAuthority> authorities) {
super(mUser.getUsername(), mUser.getPassword(), authorities);
this.mUser = mUser;
}
public MUserDTO getmUser() {
return mUser;
}
public void setmUser(MUserDTO mUser) {
this.mUser = mUser;
}
}
UserDetailsService
UserDetailsService用户获取本系统中的用户信息。具体使用方式为编写一个实现org.springframework.security.core.userdetails.UserDetailsService
的类,复写loadUserByUsername(String username)方法,通过此方法根据username查询数据库,将查出的用户信息组成一个UserDetails作为返回值返回。Spring Security会将此用户信息放入上下文中。
示例:
@Service
public class WebUserDetailsService implements UserDetailsService {
@Autowired
private MUserService mUserService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
MUserDTO user = mUserService.getByUsernanme(username);
if(null == user) {
throw new UsernameNotFoundException("用户不存在");
}
//不做角色,以后要的话在这里加载角色,现在返回空List
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
UserLoginDetails details = new UserLoginDetails(user, grantedAuthorities);
return details;
}
}
AbstractAuthenticationProcessingFilter
这个过滤器是为了拦截登录url,获取请求中的用户名、密码相应参数,然后生成用户token放到Spring上下文中。
使用到的常量类:
public interface WebLoginConstants {
/**
* 登录url
*/
String LOGIN_URL = "/login";
/**
* 登录请求方法
*/
String WEB_LOGIN_METHOD = "POST";
/**
* 用户名参数
*/
String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
/**
* 密码参数
*/
String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
}
示例:
public class WebUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
protected WebUsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher(WebLoginConstants.LOGIN_URL, WebLoginConstants.WEB_LOGIN_METHOD));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
String username;
String password;
try {
username = obtainUsername(request);
password = obtainPassword(request);
} catch (Exception e) {
throw new AuthenticationServiceException("Authentication parameter not supported ");
}
if (username == null)
username = "";
if (password == null)
password = "";
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
/**
* 从request中拿password
* @param request
* @return
*/
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(WebLoginConstants.SPRING_SECURITY_FORM_PASSWORD_KEY);
}
/**
* 从request中拿username
* @param request
* @return
*/
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(WebLoginConstants.SPRING_SECURITY_FORM_USERNAME_KEY);
}
/**
* 将试图登陆的用户的用户token存到Spring Security上下文
* @param request
* @param authRequest
*/
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
}
AbstractUserDetailsAuthenticationProvider
此类用于自定义密码校验,在这个类中,可以对比token中的密码经过处理后是否与UserDetails中的密码一致,如果一致则放行,否则抛出密码错误异常。
简单模拟加密工具:
public class MllPasswordEncoder implements PasswordEncoder {
private static MllPasswordEncoder encoder = new MllPasswordEncoder();
public static MllPasswordEncoder getInstance() {
return encoder;
}
private MllPasswordEncoder(){}
@Override
public String encode(CharSequence rawPassword) {
return String.valueOf(rawPassword) + "*";
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
if(rawPassword.equals(String.valueOf(encodedPassword) + "*")){
return true;
}
return false;
}
}
示例:
@Component
public class WebAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
@Autowired
private WebUserDetailsService webUserDetailsService;
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
String presentedPassword = authentication.getCredentials().toString();
if (!MllPasswordEncoder.getInstance()
.matches(presentedPassword, userDetails.getPassword())) {
throw new BadCredentialsException("用户名或密码不正确");
}
}
@Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
UserDetails loadedUser;
try {
loadedUser = this.webUserDetailsService.loadUserByUsername(username);
} catch (UsernameNotFoundException e) {
throw e;
} catch (Exception e) {
throw new InternalAuthenticationServiceException(e.getMessage(), e);
}
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
}
}
AuthenticationEntryPoint
用于未登录错误的处理。当用户未登录时,会调用此类中的方法:
commence(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,AuthenticationException e)
示例:
@Component
public class WebAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean {
@Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
// 直接返回未登录处理
httpServletResponse.setStatus(401);
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.sendError(401, "用户没有登陆或登陆超时,请重新登陆!");
httpServletResponse.getWriter().write("用户没有登陆或登陆超时,请重新登陆!");
}
@Override
public void afterPropertiesSet() throws Exception {
}
}
SimpleUrlAuthenticationFailureHandler
用于用户名密码校验失败的处理,用户token和查询出的用户信息不匹配或不存在时,调用此类中的方法:onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,AuthenticationException exception)
示例:
@Component
public class WebAuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler
implements AuthenticationFailureHandler {
/**
* 登录时密码或账号不正确时的操作
*/
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.setCharacterEncoding("utf-8");
response.getWriter().write("incorrect");
return;
}
}
SavedRequestAwareAuthenticationSuccessHandler
用于用户校验成功后的处理,一般会在用户登录成功进行一些后置方法的执行,比如增加在线数、保存session等等。
示例:
@Component
public class WebAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
System.out.println(SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString());
UserLoginDetails userDetails = (UserLoginDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
HttpSession session = request.getSession();
// 让以前的session失效
session.invalidate();
// 创建新的session
session = request.getSession();
session.setAttribute("SESSION_USER", userDetails);
session.setAttribute("SESSION_USERID", userDetails.getmUser().getNickname());
response.setCharacterEncoding("utf-8");
response.getWriter().write("success");
return;
}
}
拼装这些类
使用一个配置类,来将这些配置组装起来,实现个性化的安全验证。
示例:
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private WebAuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private WebAuthenticationFailHandler authenticationFailHandler;
@Autowired
WebAuthenticationEntryPoint webAuthenticationEntryPoint;
@Autowired
WebAuthenticationProvider webAuthenticationProvider;
@Autowired
WebLogoutSuccessHandler webLogoutSuccessHandler;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(webAuthenticationProvider);
auth.eraseCredentials(false);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
.logout().logoutUrl("/logout")
.logoutSuccessHandler(webLogoutSuccessHandler)
.invalidateHttpSession(true).permitAll()
.and()
.exceptionHandling()
.authenticationEntryPoint(webAuthenticationEntryPoint);
http.addFilterBefore(webUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public WebUsernamePasswordAuthenticationFilter webUsernamePasswordAuthenticationFilter() {
WebUsernamePasswordAuthenticationFilter filter = new WebUsernamePasswordAuthenticationFilter();
try {
filter.setAuthenticationManager(authenticationManagerBean());
} catch (Exception e) {
e.printStackTrace();
}
filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
filter.setAuthenticationFailureHandler(authenticationFailHandler);
return filter;
}
}
到此,简单的基于用户名密码的登录验证就完成了。如果需要加入权限控制,则需要在此基础上加入新的配置。