支持接口鉴权,支持修改密码,

This commit is contained in:
panlinlin
2021-04-14 16:33:10 +08:00
parent 123ce171d2
commit cb5849d8a1
20 changed files with 752 additions and 16 deletions

View File

@@ -0,0 +1,41 @@
package com.genersoft.iot.vmp.conf.security;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 处理匿名用户访问逻辑
*/
@Component
public class AnonymousAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final static Logger logger = LoggerFactory.getLogger(DefaultUserDetailsServiceImpl.class);
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
logger.debug("用户需要登录,访问[{}]失败AuthenticationException=[{}]", request.getRequestURI(), e.getMessage());
// 允许跨域
response.setHeader("Access-Control-Allow-Origin", "*");
// 允许自定义请求头token(允许head跨域)
response.setHeader("Access-Control-Allow-Headers", "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
response.setHeader("Content-type", "application/json;charset=UTF-8");
JSONObject jsonObject = new JSONObject();
jsonObject.put("msg", e.getMessage());
jsonObject.put("code", "-1");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
try {
response.getWriter().print(jsonObject.toJSONString());
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}

View File

@@ -0,0 +1,52 @@
package com.genersoft.iot.vmp.conf.security;
import com.genersoft.iot.vmp.conf.security.dto.LoginUser;
import com.genersoft.iot.vmp.service.IUserService;
import com.genersoft.iot.vmp.storager.dao.dto.User;
import com.github.xiaoymin.knife4j.core.util.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
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.Component;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Collection;
/**
* 用户登录认证逻辑
*/
@Component
public class DefaultUserDetailsServiceImpl implements UserDetailsService {
private final static Logger logger = LoggerFactory.getLogger(DefaultUserDetailsServiceImpl.class);
@Autowired
private IUserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (StrUtil.isBlank(username)) {
logger.info("登录用户:{} 不存在", username);
throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
}
// 查出密码
User user = userService.getUserByUsername(username);
String password = SecurityUtils.encryptPassword(user.getPassword());
user.setPassword(password);
if (user == null) {
logger.info("登录用户:{} 不存在", username);
throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
}
return new LoginUser(user, LocalDateTime.now());
}
}

View File

@@ -0,0 +1,24 @@
package com.genersoft.iot.vmp.conf.security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.web.session.InvalidSessionStrategy;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 登录超时的处理
*/
public class InvalidSessionHandler implements InvalidSessionStrategy {
private final static Logger logger = LoggerFactory.getLogger(InvalidSessionHandler.class);
@Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse httpServletResponse) throws IOException, ServletException {
String username = request.getParameter("username");
logger.info("[登录超时] - [{}]", username);
}
}

View File

@@ -0,0 +1,65 @@
package com.genersoft.iot.vmp.conf.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
private final static Logger logger = LoggerFactory.getLogger(LoginFailureHandler.class);
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
String username = request.getParameter("username");
if (e instanceof AccountExpiredException) {
// 账号过期
logger.info("[登录失败] - 用户[{}]账号过期", username);
} else if (e instanceof BadCredentialsException) {
// 密码错误
logger.info("[登录失败] - 用户[{}]密码错误", username);
} else if (e instanceof CredentialsExpiredException) {
// 密码过期
logger.info("[登录失败] - 用户[{}]密码过期", username);
} else if (e instanceof DisabledException) {
// 用户被禁用
logger.info("[登录失败] - 用户[{}]被禁用", username);
} else if (e instanceof LockedException) {
// 用户被锁定
logger.info("[登录失败] - 用户[{}]被锁定", username);
} else if (e instanceof InternalAuthenticationServiceException) {
// 内部错误
logger.error(String.format("[登录失败] - [%s]内部错误", username), e);
} else {
// 其他错误
logger.error(String.format("[登录失败] - [%s]其他错误", username), e);
}
Map<String, Object> map = new HashMap<>();
map.put("code","0");
map.put("msg","登录失败");
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(map));
}
}

View File

@@ -0,0 +1,24 @@
package com.genersoft.iot.vmp.conf.security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
private final static Logger logger = LoggerFactory.getLogger(LoginSuccessHandler.class);
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
String username = request.getParameter("username");
logger.info("[登录成功] - [{}]", username);
}
}

View File

@@ -0,0 +1,27 @@
package com.genersoft.iot.vmp.conf.security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 退出登录成功
*/
@Component
public class LogoutHandler implements LogoutSuccessHandler {
private final static Logger logger = LoggerFactory.getLogger(LogoutHandler.class);
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
String username = request.getParameter("username");
logger.info("[退出登录成功] - [{}]", username);
}
}

View File

@@ -0,0 +1,80 @@
package com.genersoft.iot.vmp.conf.security;
import com.genersoft.iot.vmp.conf.security.dto.LoginUser;
import com.genersoft.iot.vmp.storager.dao.dto.User;
import gov.nist.javax.sip.address.UserInfo;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import javax.security.sasl.AuthenticationException;
public class SecurityUtils {
/**
* 描述根据账号密码进行调用security进行认证授权 主动调
* 用AuthenticationManager的authenticate方法实现
* 授权成功后将用户信息存入SecurityContext当中
* @param username 用户名
* @param password 密码
* @param authenticationManager 认证授权管理器,
* @see AuthenticationManager
* @return UserInfo 用户信息
*/
public static LoginUser login(String username, String password, AuthenticationManager authenticationManager) throws AuthenticationException {
//使用security框架自带的验证token生成器 也可以自定义。
UsernamePasswordAuthenticationToken token =new UsernamePasswordAuthenticationToken(username,password);
Authentication authenticate = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authenticate);
LoginUser user = (LoginUser) authenticate.getPrincipal();
return user;
}
/**
* 获取当前登录的所有认证信息
* @return
*/
public static Authentication getAuthentication(){
SecurityContext context = SecurityContextHolder.getContext();
return context.getAuthentication();
}
/**
* 获取当前登录用户信息
* @return
*/
public static LoginUser getUserInfo(){
Authentication authentication = getAuthentication();
if(authentication!=null){
Object principal = authentication.getPrincipal();
if(principal!=null){
LoginUser user = (LoginUser) authentication.getPrincipal();
return user;
}
}
return null;
}
/**
* 获取当前登录用户ID
* @return
*/
public static int getUserId(){
LoginUser user = getUserInfo();
return user.getId();
}
/**
* 生成BCryptPasswordEncoder密码
*
* @param password 密码
* @return 加密字符串
*/
public static String encryptPassword(String password) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.encode(password);
}
}

View File

@@ -0,0 +1,144 @@
package com.genersoft.iot.vmp.conf.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* 配置Spring Security
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DefaultUserDetailsServiceImpl userDetailsService;
/**
* 登出成功的处理
*/
@Autowired
private LoginFailureHandler loginFailureHandler;
/**
* 登录成功的处理
*/
@Autowired
private LoginSuccessHandler loginSuccessHandler;
/**
* 登出成功的处理
*/
@Autowired
private LogoutHandler logoutHandler;
/**
* 未登录的处理
*/
@Autowired
private AnonymousAuthenticationEntryPoint anonymousAuthenticationEntryPoint;
// /**
// * 超时处理
// */
// @Autowired
// private InvalidSessionHandler invalidSessionHandler;
// /**
// * 顶号处理
// */
// @Autowired
// private SessionInformationExpiredHandler sessionInformationExpiredHandler;
// /**
// * 登录用户没有权限访问资源
// */
// @Autowired
// private LoginUserAccessDeniedHandler accessDeniedHandler;
/**
* 描述: 静态资源放行,这里的放行,是不走 Spring Security 过滤器链
**/
@Override
public void configure(WebSecurity web) {
// 可以直接访问的静态数据
web.ignoring()
.antMatchers("/")
.antMatchers("/css/**")
.antMatchers("/img/**")
.antMatchers("/fonts/**")
.antMatchers("/index.html")
.antMatchers("/doc.html") // "/webjars/**", "/swagger-resources/**", "/v3/api-docs/**"
.antMatchers("/webjars/**")
.antMatchers("/swagger-resources/**")
.antMatchers("/v3/api-docs/**")
.antMatchers("/js/**");
}
/**
* 配置认证方式
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
// 设置不隐藏 未找到用户异常
provider.setHideUserNotFoundExceptions(true);
// 用户认证service - 查询数据库的逻辑
provider.setUserDetailsService(userDetailsService);
// 设置密码加密算法
provider.setPasswordEncoder(passwordEncoder());
auth.authenticationProvider(provider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.authorizeRequests()
// 放行接口
.antMatchers("/api/user/login","/index/hook/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
// 异常处理(权限拒绝、登录失效等)
.and().exceptionHandling()
.authenticationEntryPoint(anonymousAuthenticationEntryPoint)//匿名用户访问无权限资源时的异常处理
// .accessDeniedHandler(accessDeniedHandler)//登录用户没有权限访问资源
// 登入
.and().formLogin().permitAll()//允许所有用户
.successHandler(loginSuccessHandler)//登录成功处理逻辑
.failureHandler(loginFailureHandler)//登录失败处理逻辑
// 登出
.and().logout().logoutUrl("/api/user/logout").permitAll()//允许所有用户
.logoutSuccessHandler(logoutHandler)//登出成功处理逻辑
.deleteCookies("JSESSIONID")
// 会话管理
// .and().sessionManagement().invalidSessionStrategy(invalidSessionHandler) // 超时处理
// .maximumSessions(1)//同一账号同时登录最大用户数
// .expiredSessionStrategy(sessionInformationExpiredHandler) // 顶号处理
;
}
/**
* 描述: 密码加密算法 BCrypt 推荐使用
**/
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 描述: 注入AuthenticationManager管理器
**/
@Override
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}

View File

@@ -0,0 +1,95 @@
package com.genersoft.iot.vmp.conf.security.dto;
import com.genersoft.iot.vmp.storager.dao.dto.User;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.core.userdetails.UserDetails;
import java.time.LocalDateTime;
import java.util.Collection;
public class LoginUser implements UserDetails, CredentialsContainer {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
/**
* 用户
*/
private User user;
/**
* 登录时间
*/
private LocalDateTime loginTime;
public LoginUser(User user, LocalDateTime loginTime) {
this.user = user;
this.loginTime = loginTime;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
/**
* 账户是否未过期,过期无法验证
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 指定用户是否解锁,锁定的用户无法进行身份验证
* <p>
* 密码锁定
* </p>
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 用户是否被启用或禁用。禁用的用户无法进行身份验证。
*/
@Override
public boolean isEnabled() {
return true;
}
/**
* 认证完成后,擦除密码
*/
@Override
public void eraseCredentials() {
user.setPassword(null);
}
public int getId() {
return user.getId();
}
}

View File

@@ -8,5 +8,5 @@ public interface IUserService {
boolean changePassword(int id, String password);
User getUserByUsername(String username);
}

View File

@@ -24,4 +24,9 @@ public class UserServiceImpl implements IUserService {
user.setPassword(password);
return userMapper.update(user) > 0;
}
@Override
public User getUserByUsername(String username) {
return userMapper.getUserByUsername(username);
}
}

View File

@@ -28,4 +28,7 @@ public interface UserMapper {
@Select("select * FROM user WHERE id= #{id}")
User selectById(int id);
@Select("select * FROM user WHERE username= #{username}")
User getUserByUsername(String username);
}

View File

@@ -1,5 +1,7 @@
package com.genersoft.iot.vmp.vmanager.user;
import com.genersoft.iot.vmp.conf.security.SecurityUtils;
import com.genersoft.iot.vmp.conf.security.dto.LoginUser;
import com.genersoft.iot.vmp.service.IUserService;
import com.genersoft.iot.vmp.storager.dao.dto.User;
import io.swagger.annotations.Api;
@@ -8,11 +10,12 @@ import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.security.sasl.AuthenticationException;
@Api(tags = "用户管理")
@CrossOrigin
@@ -21,21 +24,46 @@ import org.springframework.web.bind.annotation.RestController;
public class UserController {
@Autowired
private IUserService userService;
AuthenticationManager authenticationManager;
@Autowired
IUserService userService;
@ApiOperation("登录")
@ApiImplicitParams({
@ApiImplicitParam(name = "username", value = "用户名", dataTypeClass = String.class),
@ApiImplicitParam(name = "password", value = "密码32md5加密", dataTypeClass = String.class),
@ApiImplicitParam(name = "password", value = "密码32md5加密", dataTypeClass = String.class),
})
@GetMapping("/login")
public String login(String username, String password){
User user = userService.getUser(username, password);
LoginUser user = null;
try {
user = SecurityUtils.login(username, password, authenticationManager);
} catch (AuthenticationException e) {
e.printStackTrace();
return "fail";
}
if (user != null) {
return "success";
}else {
return "fail";
}
}
@ApiOperation("修改密码")
@ApiImplicitParams({
@ApiImplicitParam(name = "username", value = "用户名", dataTypeClass = String.class),
@ApiImplicitParam(name = "password", value = "密码未md5加密的密码", dataTypeClass = String.class),
})
@PostMapping("/changePassword")
public String changePassword(String password){
// 获取当前登录用户id
int userId = SecurityUtils.getUserId();
boolean result = userService.changePassword(userId, DigestUtils.md5DigestAsHex(password.getBytes()));
if (result) {
return "success";
}else {
return "fail";
}
}
}