1认证

认证:辨别用户是否本系统用户。

优势:1 提供多样式的加密方法

2 提供多样式的用户存储方式

3 使用者无需关注验证封装业务 只需要提供查询方法即可

4 多样式的认证方式

5 提供用户信息获取方式

可扩展的功能

1 记住我

2 邮箱验证

3 手机验证

4 验证码验证

配置详解

spring security统一实现WebSecurityConfigurerAdapter接口 按照以下需求添加以下配置 就可以整合成功

@Configuration
@EnableWebSecurity // 开启springsecurity过滤链 filter
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启注解方法级别权限控制
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

//实现输入是明文 存储到数据库为密文 写死即可
@Bean
public PasswordEncoder passwordEncoder() {
// 明文+随机盐值》加密存储
return new BCryptPasswordEncoder();
}

//用户验证的业务流程 也就是查询用户的业务代码
@Autowired
UserDetailsService customUserDetailsService;

/**
* 认证管理器: 将上文查询用户是否存在的service按样式写入 修改service 其余写死 还有密码写死或存放内存中等方式 这里不讨论
* 1. 认证信息(用户名,密码)
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 数据库存储的密码必须是加密后的也就是必须要有passwordEncoder,不然会报错:There is no PasswordEncoder mapped for
auth.userDetailsService(customUserDetailsService);
}

//验证码配置
@Autowired
private ImageCodeValidateFilter imageCodeValidateFilter;

//当验证成功后可以返回json或者路径 但是现在基于前后台分离 大多数都是返回json AuthenticationSuccessHandler为成功后转为json的处理 按照本文配置即可
@Autowired
private AuthenticationSuccessHandler customAuthenticationSuccessHandler;

//当验证成功后可以返回json或者路径 但是现在基于前后台分离 大多数都是返回json AuthenticationFailureHandler为失败后转为json的处理 按照本文配置即可
@Autowired
private AuthenticationFailureHandler customAuthenticationFailureHandler;

//建立数据源
@Autowired
DataSource dataSource;


@Autowired
private InvalidSessionStrategy invalidSessionStrategy;

/**
* 当同个用户session数量超过指定值之后 ,会调用这个实现类
*/
@Autowired
private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;

/**
* 持久化token
* @return
*/
@Bean
public JdbcTokenRepositoryImpl jdbcTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
// 是否启动项目时自动创建表,true自动创建
// jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
/**
* 核心拦截器 当你认证成功之后 ,springsecurity它会重写向到你上一次请求上
* 资源权限配置:
* 1. 被拦截的资源
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//调用验证码过滤器 下文会详细介绍
http.addFilterBefore(imageCodeValidateFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin() // 表单登录方式
.loginPage("/login/page") //登录页的页面地址
.loginProcessingUrl("/login/form") // 登录表单提交处理url, 默认是/login
.usernameParameter("username") //默认的是 username
.passwordParameter("password") // 默认的是 password
.successHandler(customAuthenticationSuccessHandler) //登录成功返回的json
.failureHandler(customAuthenticationFailureHandler) //登录失败返回的json
.and() //每个类型的配置 以.and()间隔 相当于;
.authorizeRequests() // 授权请求
.antMatchers("/login/page",
"/code/image","/mobile/page", "/code/mobile",
"/code/image",
"/code/mobile",
"/mobile/page"
).permitAll() // 放行/login/page不需要认证可访问 因为如果在调用验证接口时还需要权限 那么就没有入口了 所以一些不需要登录就能访问的接口在此配置

// 此处是鉴权
// 有 sys:user 权限的可以访问任意请求方式的/role
.antMatchers("/user").hasAuthority("sys:user")
// 有 sys:role 权限的可以访问 get方式的/role
.antMatchers(HttpMethod.GET,"/role").hasAuthority("sys:role")
.antMatchers(HttpMethod.GET, "/permission")
// ADMIN 注意角色会在前面加上前缀 ROLE_ , 也就是完整的是 ROLE_ADMIN, ROLE_ROOT
.access("hasAuthority('sys:premission') or hasAnyRole('ADMIN', 'ROOT')")
// 此处是鉴权

.anyRequest().authenticated() //所有访问该应用的http请求都要通过身份认证才可以访问
.and()
.rememberMe() //记住我功能
//记住功能配置
.tokenRepository(jdbcTokenRepository()) //保存token信息
.tokenValiditySeconds(604800) //记住我有效时长
.and()
.sessionManagement()// session管理
.invalidSessionStrategy(invalidSessionStrategy) //当session失效后的处理类 //.expiredSessionStrategy(sessionInformationExpiredStrategy)// 当用户达到最大session数后,则调用此处的实现
.maximumSessions(1) // 每个用户在系统中最多可以有多少个session
.maxSessionsPreventsLogin(true) // 当一个用户达到最大session数,则不允许后面再登录
.sessionRegistry(sessionRegistry())
.and().and()
.logout()//登出相关
.addLogoutHandler(customLogoutHandler) // 退出清除缓存
.logoutUrl("/user/logout") // 退出请求路径
.logoutSuccessUrl("/mobile/page") //退出成功后跳转地址
.deleteCookies("JSESSIONID") // 退出后删除什么cookie值
;// 注意不要少了分号
http.csrf().disable(); // 关闭跨站请求伪造
}

/**
* 退出清除缓存
*/
@Autowired
private CustomLogoutHandler customLogoutHandler;

/**
* 为了解决退出重新登录问题
* @return
*/
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
/**
* 一般是针对静态资源放行
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web){
web.ignoring().antMatchers("/js/**", "/css/**");
}
}

清除缓存方法

@Component
public class CustomLogoutHandler implements LogoutHandler {

@Autowired
private SessionRegistry sessionRegistry;

@Override
public void logout(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) {
// 退出之后 ,将对应session从缓存中清除 SessionRegistryImpl.principals
sessionRegistry.removeSessionInformation(request.getSession().getId());
}
}

编写user验证方法类

需要继承UserDetailsService 为jar包提供

public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

获取SysUser实体方法和获取此人菜单的方法 这里不多说

public abstract class AbstractUserDetailsService implements UserDetailsService {

@Autowired
private SysPermissionService sysPermissionService;

/**
* 这个方法交给子类去实现它,查询用户信息
*
* @param usernameOrMobile 用户名或者手机号
* @return
*/
public abstract SysUser findSysUser(String usernameOrMobile);

@Override
public UserDetails loadUserByUsername(String usernameOrMobile) throws UsernameNotFoundException {
// 1. 通过请求的用户名去数据库中查询用户信息
SysUser sysUser = findSysUser(usernameOrMobile);
// 通过用户id去获取权限信息
findSysPermission(sysUser);
return sysUser;
}

private void findSysPermission(SysUser sysUser) {
if (sysUser == null) {
throw new UsernameNotFoundException("用户名或密码错误");
}
// 2. 查询该用户有哪一些权限
List<SysPermission> permissions = sysPermissionService.findByUserId(sysUser.getId());
if (CollectionUtils.isEmpty(permissions)) {
return;
}
// 在左侧菜单 动态渲染会使用,目前先把它都传入
sysUser.setPermissions(permissions);
// 3. 封装权限信息
List<GrantedAuthority> authorities = Lists.newArrayList();
for (SysPermission sp : permissions) {
// 权限标识
String code = sp.getCode();
authorities.add(new SimpleGrantedAuthority(code));
}
sysUser.setAuthorities(authorities);
}
}
@Component("customUserDetailsService")
public class CustomUserDetailsService extends AbstractUserDetailsService{
Logger logger = LoggerFactory.getLogger(getClass());

@Autowired // 不能删掉,不然报错
PasswordEncoder passwordEncoder;

@Autowired
SysUserService sysUserService;

@Override
public SysUser findSysUser(String usernameOrMobile) {
logger.info("请求认证的用户名: " + usernameOrMobile);
// 1. 通过请求的用户名去数据库中查询用户信息
return sysUserService.findByUsername(usernameOrMobile);
}
}
//此类没有注入,因为楼主并没有使用
@Component("customAuthenticationSuccessHandler")
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

@Autowired(required = false) // 容器中可以不需要有接口的实现,如果有则自动注入
AuthenticationSuccessListener authenticationSuccessListener;

@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
if(authenticationSuccessListener != null) {
// 当认证之后 ,调用此监听,进行后续处理,比如加载用户权限菜单
authenticationSuccessListener.successListener(request, response, authentication);
}
if(LoginResponseType.JSON.equals(
"post")) {
// 认证成功后,响应JSON字符串
MengxueguResult result = MengxueguResult.ok("认证成功");
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(result.toJsonString());
}else {
//重定向到上次请求的地址上,引发跳转到认证页面的地址
logger.info("authentication: " + JSON.toJSONString(authentication));
super.onAuthenticationSuccess(request, response, authentication);
}

}
}
@Component("customAuthenticationFailureHandler")
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

/**
*
* @param exception 认证失败时抛出异常
*/
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
if(LoginResponseType.JSON.equals("post")) {
// 认证失败响应JSON字符串,
MengxueguResult result = MengxueguResult.build(HttpStatus.UNAUTHORIZED.value(), exception.getMessage());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(result.toJsonString());
}else {
// 重写向回认证页面,注意加上 ?error
// super.setDefaultFailureUrl(securityProperties.getAuthentication().getLoginPage()+"?error");
// 获取上一次请求路径
String referer = request.getHeader("Referer");
logger.info("referer:" + referer);

// 如果下面有值,则认为是多端登录,直接返回一个登录地址
Object toAuthentication = request.getAttribute("toAuthentication");
String lastUrl = toAuthentication != null ? "/login/page"
: StringUtils.substringBefore(referer,"?");
logger.info("上一次请求的路径 :" + lastUrl);
super.setDefaultFailureUrl(lastUrl+"?error");
super.onAuthenticationFailure(request, response, exception);
}
}
}