只需要通过账号即可登录
-
通过账号即可登录 绕过密码验证进行登录
-
SecurityConfig
- 作用: SecurityConfig 用于定义应用程序的安全策略和行为,比如配置哪些 URL 需要保护,哪些可以公开访问,使用什么样的身份验证机制,如何处理跨域请求(CORS)等。
- 内容: 典型的 SecurityConfig 中会包括一些 Bean 定义和安全规则配置。例如,配置自定义的 AuthenticationProvider、
AuthenticationManager
、密码编码器(如 BCryptPasswordEncoder)等。
- AuthenticationManager
- 作用: 当用户尝试登录时,Spring Security 会将登录请求传递给 AuthenticationManager。AuthenticationManager 然后委托给
一个或多个 AuthenticationProvider
来执行具体的认证逻辑。 - 如何配置: 在 SecurityConfig 中,通常通过 authenticationManagerBean() 方法或者直接在 filterChain 方法中配置。也可以通过 AuthenticationManagerBuilder 来配置多个 AuthenticationProvider
@Bean
public AuthenticationManager authenticationManager()
{
// 默认
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
// wx登陆鉴权 Provider
WxCodeAuthenticationProvider wxCodeAuthenticationProvider = new WxCodeAuthenticationProvider();
wxCodeAuthenticationProvider.setUserDetailsService(userDetailsServiceByOpenId);
List<AuthenticationProvider> providers = new ArrayList<>();
providers.add(wxCodeAuthenticationProvider);
providers.add(daoAuthenticationProvider);
return new ProviderManager(providers);
}
- AuthenticationProvider
- 作用: AuthenticationProvider 接受来自
AuthenticationManager
的认证请求,验证请求中的凭证(如用户名、密码、令牌等),并返回一个已认证的 Authentication 对象或者抛出异常。 - 配置: 通常在 SecurityConfig 中作为一个 Bean 注册,或者通过 AuthenticationManagerBuilder 直接配置到 AuthenticationManager 中。
需要增加的文件
代码
package com.ruoyi.framework.web.phone;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import java.util.Collection;
public class WxCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
/**
* 在 UsernamePasswordAuthenticationToken 中该字段代表登录的用户名,
* 在这里就代表登录的openId
*/
private final Object principal;
/**
* 构建一个没有鉴权的 WxCodeAuthenticationToken
*/
public WxCodeAuthenticationToken(Object principal) {
super(null);
this.principal = principal;
setAuthenticated(false);
}
/**
* 构建拥有鉴权的 WxCodeAuthenticationToken
*/
public WxCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
// must use super, as we override
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
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();
}
}
package com.ruoyi.framework.web.phone;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.AuthenticationException;
public class WxCodeAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
WxCodeAuthenticationToken authenticationToken = (WxCodeAuthenticationToken) authentication;
String openId = (String) authenticationToken.getPrincipal();
UserDetails userDetails = userDetailsService.loadUserByUsername(openId);
// 此时鉴权成功后,应当重新 new 一个拥有鉴权的 authenticationResult 返回
WxCodeAuthenticationToken authenticationResult = new WxCodeAuthenticationToken(userDetails, userDetails.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
// 判断 authentication 是不是 WxCodeAuthenticationToken 的子类或子接口
return WxCodeAuthenticationToken.class.isAssignableFrom(authentication);
}
public UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
}
package com.ruoyi.framework.web.service;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.UserStatus;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service("UserDetailsByOpenIdServiceImpl")
public class UserDetailsByOpenIdServiceImpl implements UserDetailsService {
@Autowired
private ISysUserService userService;
@Autowired
private SysPermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String openId) throws UsernameNotFoundException {
SysUser user = userService.selectUserByUserName(openId);
if (StringUtils.isNull(user)) {
throw new UsernameNotFoundException("登录用户:" + openId + " 不存在");
} else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
throw new BaseException("对不起,您的账号:" + openId + " 已被删除");
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
throw new BaseException("对不起,您的账号:" + openId + " 已停用");
}
// 和若依的区别 是没有验证密码
return createLoginUser(user);
}
public UserDetails createLoginUser(SysUser user) {
return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
}
}
/**
* spring security配置
*
* @author ruoyi
*/
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Configuration
public class SecurityConfig
{
/**
* 自定义用户认证逻辑
*/
@Autowired
@Qualifier("UserDetailsServiceImpl") // 需要添加 不然会冲突
private UserDetailsService userDetailsService;
/**
* 自定义用户(手机号验证码)认证逻辑
*/
@Autowired
@Qualifier("UserDetailsByOpenIdServiceImpl") // 新增
private UserDetailsService userDetailsServiceByOpenId;
/**
* 认证失败处理类
*/
@Autowired
private AuthenticationEntryPointImpl unauthorizedHandler;
/**
* 退出处理类
*/
@Autowired
private LogoutSuccessHandlerImpl logoutSuccessHandler;
/**
* token认证过滤器
*/
@Autowired
private JwtAuthenticationTokenFilter authenticationTokenFilter;
/**
* 跨域过滤器
*/
@Autowired
private CorsFilter corsFilter;
/**
* 允许匿名访问的地址
*/
@Autowired
private PermitAllUrlProperties permitAllUrl;
/**
* 身份验证实现 需要修改
*/
@Bean
public AuthenticationManager authenticationManager()
{
// 默认
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
// wx登陆鉴权 Provider
WxCodeAuthenticationProvider wxCodeAuthenticationProvider = new WxCodeAuthenticationProvider();
wxCodeAuthenticationProvider.setUserDetailsService(userDetailsServiceByOpenId);
List<AuthenticationProvider> providers = new ArrayList<>();
providers.add(wxCodeAuthenticationProvider);
providers.add(daoAuthenticationProvider);
return new ProviderManager(providers);
}
/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Bean
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception
{
return httpSecurity
// CSRF禁用,因为不使用session
.csrf(csrf -> csrf.disable())
// 禁用HTTP响应标头
.headers((headersCustomizer) -> {
headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
})
// 认证失败处理类
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
// 基于token,所以不需要session
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 注解标记允许匿名访问的url
.authorizeHttpRequests((requests) -> {
permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
requests.antMatchers("/login","/loginPhone", "/register", "/captchaImage").permitAll()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
})
// 添加Logout filter
.logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
// 添加JWT filter
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
// 添加CORS filter
.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
.addFilterBefore(corsFilter, LogoutFilter.class)
.build();
}
/**
* 强散列哈希加密实现
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder()
{
return new BCryptPasswordEncoder();
}
}
使用方法
@Component
public class SysLoginService
{
// 将账号和密码封装成一个对象
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
// 将这个账号密码 推入到认证的对象上
AuthenticationContextHolder.setContext(authenticationToken);
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
// 将账号和密码进行验证
authentication = authenticationManager.authenticate(authenticationToken);
// 以下是新增
public String loginPhone(String username)
{
// 用户验证
Authentication authentication = null;
SysUser sysUser = userService.selectUserByUserName(username);
if (sysUser == null) {
throw new ServiceException("用户不存在");
}
try
{
// 重要 这里去构建
authentication = authenticationManager
.authenticate(new WxCodeAuthenticationToken(username));
}
catch (Exception e)
{
if (e instanceof BadCredentialsException)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
else
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
throw new ServiceException(e.getMessage());
}
}
finally
{
AuthenticationContextHolder.clearContext();
}
// 记录登录信息
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
// 获取认证登录信息
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
// 记录登录信息
recordLoginInfo(loginUser.getUserId());
// 生成token
return tokenService.createToken(loginUser);
}
其他登录方法类似