第一种方式 直接在Controller 中校验
@RestController
public class LoginController extends AbstractController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private HttpServletRequest request;
/**
* 登录
* @param userAccount 登录账号
* @param userPwd 登录密码
* @param verifyCode 验证码
* @return
*/
@PostMapping("login/enter")
public RestfulVo login(String userAccount, String userPwd,String verifyCode ){
System.out.println("进入了 controller 中的登录方法 ............. ");
if(StringUtils.isBlank(verifyCode) || !verifyCode.trim().equals("1234")){
return ResultUtil.fail("验证码错误.");
}
//登录 身份认证
// 这句代码会自动执行咱们自定义的 "MyUserDetailService.java" 身份认证类
//1: 将用户名和密码封装成UsernamePasswordAuthenticationToken new UsernamePasswordAuthenticationToken(userAccount, userPwd)
//2: 将UsernamePasswordAuthenticationToken传给AuthenticationManager进行身份认证 authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(userAccount, userPwd));
//3: 认证完毕,返回一个认证后的身份: Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(userAccount, userPwd));
//4: 认证后,存储到SecurityContext里 SecurityContext securityContext = SecurityContextHolder.getContext();securityContext.setAuthentication(authentication);
//UsernamePasswordAuthenticationToken继承AbstractAuthenticationToken实现Authentication
//当在页面中输入用户名和密码之后首先会进入到UsernamePasswordAuthenticationToken验证(Authentication),注意用户名和登录名都是页面传来的值
//然后生成的Authentication会被交由AuthenticationManager来进行管理
//而AuthenticationManager管理一系列的AuthenticationProvider,
//而每一个Provider都会通UserDetailsService和UserDetail来返回一个
//以UsernamePasswordAuthenticationToken实现的带用户名和密码以及权限的Authentication
try {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(userAccount, userPwd));
//将身份 存储到SecurityContext里
SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(authentication);
request.getSession().setAttribute("SPRING_SECURITY_CONTEXT", securityContext); // 这个非常重要,否则验证后将无法登陆
return ResultUtil.success("登录成功.");
}catch (AuthenticationException e){
e.printStackTrace();
return ResultUtil.fail("用户或者密码错误.");
}
}
}
WebSecurityConfig 配置类中配置 需要把.loginProcessingUrl("/auth/v1/api/login/enter") 指向controller中的方法地址
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("不需要认证的url:"+antMatchers);
//super.configure(http);
//关闭csrf验证
http.csrf().disable()
// 基于token,所以不需要session 如果基于session 则表使用这段代码
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
//对请求进行认证 url认证配置顺序为:1.先配置放行不需要认证的 permitAll() 2.然后配置 需要特定权限的 hasRole() 3.最后配置 anyRequest().authenticated()
.authorizeRequests()
// 所有 /oauth/v1/api/login/ 请求的都放行 不做认证即不需要登录即可访问
.antMatchers(antMatchers.split(",")).permitAll()
//.antMatchers("/auth/v1/api/login/**","/auth/v1/api/module/tree/**","/auth/v1/api/grid/**").permitAll()
// 对于获取token的rest api要允许匿名访问
.antMatchers("oauth/**").permitAll()
// 其他请求都需要进行认证,认证通过够才能访问 待考证:如果使用重定向 httpServletRequest.getRequestDispatcher(url).forward(httpServletRequest,httpServletResponse); 重定向跳转的url不会被拦截(即在这里配置了重定向的url需要特定权限认证不起效),但是如果在Controller 方法上配置了方法级的权限则会进行拦截
.anyRequest().authenticated()
.and().exceptionHandling()
// 认证配置当用户请求了一个受保护的资源,但是用户没有通过登录认证,则抛出登录认证异常,MyAuthenticationEntryPointHandler类中commence()就会调用
.authenticationEntryPoint(myAuthenticationEntryPoint())
//用户已经通过了登录认证,在访问一个受保护的资源,但是权限不够,则抛出授权异常,MyAccessDeniedHandler类中handle()就会调用
.accessDeniedHandler(myAccessDeniedHandler())
.and()
//
.formLogin()
// 登录url
// .loginProcessingUrl("/auth/v1/api/login/entry") // 此登录url 和Controller 无关系
.loginProcessingUrl("/auth/v1/api/login/enter") //使用自己定义的Controller 中的方法 登录会进入Controller 中的方法
// username参数名称 后台接收前端的参数名
.usernameParameter("userAccount")
//登录密码参数名称 后台接收前端的参数名
.passwordParameter("userPwd")
//登录成功跳转路径
.successForwardUrl("/")
//登录失败跳转路径
.failureUrl("/")
//登录页面路径
.loginPage("/")
.permitAll()
//登录成功后 MyAuthenticationSuccessHandler类中onAuthenticationSuccess()被调用
.successHandler(myAuthenticationSuccessHandler())
//登录失败后 MyAuthenticationFailureHandler 类中onAuthenticationFailure()被调用
.failureHandler(myAuthenticationFailureHandler())
.and()
.logout()
//退出系统url
.logoutUrl("/auth/v1/api/login/logout")
//退出系统后的url跳转
.logoutSuccessUrl("/")
//退出系统后的 业务处理
.logoutSuccessHandler(myLogoutSuccessHandler())
.permitAll()
.invalidateHttpSession(true)
.and()
//登录后记住用户,下次自动登录,数据库中必须存在名为persistent_logins的表
// 勾选Remember me登录会在PERSISTENT_LOGINS表中,生成一条记录
.rememberMe()
//cookie的有效期(秒为单位
.tokenValiditySeconds(3600);
}
如果出现AuthenticationManager 无法找到则 需要在WebSecurityConfig 配置类中加上如下
/**
* 解决 无法直接注入 AuthenticationManager
* @return
* @throws Exception
*/
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
第二种通过继承 UsernamePasswordAuthenticationFilter
/***
* 自定义 登录时认证
*/
@Slf4j
public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public MyUsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager,AuthenticationSuccessHandler successHandler,AuthenticationFailureHandler failureHandler){
this.setFilterProcessesUrl("/auth/v1/api/login/entry"); //这句代码很重要,设置登陆的url 要和 WebSecurityConfig 配置类中的.loginProcessingUrl("/auth/v1/api/login/entry") 一致,如果不配置则无法执行 重写的attemptAuthentication 方法里面而是执行了父类UsernamePasswordAuthenticationFilter的attemptAuthentication()
this.setAuthenticationManager(authenticationManager); // AuthenticationManager 是必须的
this.setAuthenticationSuccessHandler(successHandler); //设置自定义登陆成功后的业务处理
this.setAuthenticationFailureHandler(failureHandler); //设置自定义登陆失败后的业务处理
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//校验验证码
String verifyCode = request.getParameter("verifyCode");
if(!checkValidateCode(verifyCode)){
ResultUtil.writeJavaScript(response,ErrorCodeEnum.FAIL,"验证码错误.");
return null;
}
//设置获取 username 的属性字段 js传到后台接收数据的参数名
this.setUsernameParameter("userAccount");
//设置获取password 的属性字段 js传到后台接收数据的参数名
this.setPasswordParameter("userPwd");
return super.attemptAuthentication(request, response);
}
/**
* 验证 验证码是否正确
* @param verifyCode
* @return
*/
private boolean checkValidateCode(String verifyCode){
if(StringUtils.isBlank(verifyCode) || !verifyCode.trim().equals("1234")){
return false;
}
return true;
}
}
WebSecurityConfig 配置类中 需要加入自定义UsernamePasswordAuthenticationFilter替代原有Filter
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("不需要认证的url:"+antMatchers);
//super.configure(http);
//关闭csrf验证
http.csrf().disable()
// 基于token,所以不需要session 如果基于session 则表使用这段代码
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
//对请求进行认证 url认证配置顺序为:1.先配置放行不需要认证的 permitAll() 2.然后配置 需要特定权限的 hasRole() 3.最后配置 anyRequest().authenticated()
.authorizeRequests()
// 所有 /oauth/v1/api/login/ 请求的都放行 不做认证即不需要登录即可访问
.antMatchers(antMatchers.split(",")).permitAll()
//.antMatchers("/auth/v1/api/login/**","/auth/v1/api/module/tree/**","/auth/v1/api/grid/**").permitAll()
// 对于获取token的rest api要允许匿名访问
.antMatchers("oauth/**").permitAll()
// 其他请求都需要进行认证,认证通过够才能访问 待考证:如果使用重定向 httpServletRequest.getRequestDispatcher(url).forward(httpServletRequest,httpServletResponse); 重定向跳转的url不会被拦截(即在这里配置了重定向的url需要特定权限认证不起效),但是如果在Controller 方法上配置了方法级的权限则会进行拦截
.anyRequest().authenticated()
.and().exceptionHandling()
// 认证配置当用户请求了一个受保护的资源,但是用户没有通过登录认证,则抛出登录认证异常,MyAuthenticationEntryPointHandler类中commence()就会调用
.authenticationEntryPoint(myAuthenticationEntryPoint())
//用户已经通过了登录认证,在访问一个受保护的资源,但是权限不够,则抛出授权异常,MyAccessDeniedHandler类中handle()就会调用
.accessDeniedHandler(myAccessDeniedHandler())
.and()
//
.formLogin()
// 登录url
.loginProcessingUrl("/auth/v1/api/login/entry") // 此登录url 和Controller 无关系
// .loginProcessingUrl("/auth/v1/api/login/enter") //使用自己定义的Controller 中的方法 登录会进入Controller 中的方法
// username参数名称 后台接收前端的参数名
.usernameParameter("userAccount")
//登录密码参数名称 后台接收前端的参数名
.passwordParameter("userPwd")
.permitAll()
.and()
.logout()
//退出系统url
.logoutUrl("/auth/v1/api/login/logout")
//退出系统后的url跳转
.logoutSuccessUrl("/")
//退出系统后的 业务处理
.logoutSuccessHandler(myLogoutSuccessHandler())
.permitAll()
.invalidateHttpSession(true)
.and()
//登录后记住用户,下次自动登录,数据库中必须存在名为persistent_logins的表
// 勾选Remember me登录会在PERSISTENT_LOGINS表中,生成一条记录
.rememberMe()
//cookie的有效期(秒为单位
.tokenValiditySeconds(3600);
// 加入自定义UsernamePasswordAuthenticationFilter替代原有Filter
http.addFilterAt(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
// 禁用缓存
http.headers().cacheControl();
}
/**
* 注册 自定义登录成功 处理 bean
* @return
*/
@Bean
public AuthenticationSuccessHandler myAuthenticationSuccessHandler(){
return new MyAuthenticationSuccessHandler();
}
/**
* 注册 自定义登录失败 处理 bean
* @return
*/
@Bean
public AuthenticationFailureHandler myAuthenticationFailureHandler(){
return new MyAuthenticationFailureHandler();
}
/**
* 验证登录验证码
* @return
* @throws Exception
*/
@Bean
public UsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter() throws Exception {
return new MyUsernamePasswordAuthenticationFilter(authenticationManagerBean(),myAuthenticationSuccessHandler(),myAuthenticationFailureHandler());
}