panlinlin
2021-04-14 cb5849d8a14f55241c44bdf6724b18de7950564d
支持接口鉴权,支持修改密码,
10个文件已修改
10个文件已添加
768 ■■■■■ 已修改文件
pom.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/security/AnonymousAuthenticationEntryPoint.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/security/DefaultUserDetailsServiceImpl.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/security/InvalidSessionHandler.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/security/LoginFailureHandler.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/security/LogoutHandler.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/security/dto/LoginUser.java 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IUserService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/UserMapper.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/config/index.js 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/DeviceList.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/Login.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/UiHeader.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/changePassword.vue 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/main.js 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -67,6 +67,10 @@
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- druid数据库连接池 -->
        <dependency>
src/main/java/com/genersoft/iot/vmp/conf/security/AnonymousAuthenticationEntryPoint.java
New 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();
        }
    }
}
src/main/java/com/genersoft/iot/vmp/conf/security/DefaultUserDetailsServiceImpl.java
New 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());
    }
}
src/main/java/com/genersoft/iot/vmp/conf/security/InvalidSessionHandler.java
New 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);
    }
}
src/main/java/com/genersoft/iot/vmp/conf/security/LoginFailureHandler.java
New 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));
    }
}
src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java
New 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);
    }
}
src/main/java/com/genersoft/iot/vmp/conf/security/LogoutHandler.java
New 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);
    }
}
src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java
New 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);
    }
}
src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java
New 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();
    }
}
src/main/java/com/genersoft/iot/vmp/conf/security/dto/LoginUser.java
New 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();
    }
}
src/main/java/com/genersoft/iot/vmp/service/IUserService.java
@@ -8,5 +8,5 @@
    boolean changePassword(int id, String password);
    User getUserByUsername(String username);
}
src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java
@@ -24,4 +24,9 @@
        user.setPassword(password);
        return userMapper.update(user) > 0;
    }
    @Override
    public User getUserByUsername(String username) {
        return userMapper.getUserByUsername(username);
    }
}
src/main/java/com/genersoft/iot/vmp/storager/dao/UserMapper.java
@@ -28,4 +28,7 @@
    @Select("select * FROM user WHERE id= #{id}")
    User selectById(int id);
    @Select("select * FROM user WHERE username= #{username}")
    User getUserByUsername(String username);
}
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java
@@ -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.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 @@
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 = "密码(32未md5加密)", dataTypeClass = String.class),
            @ApiImplicitParam(name = "password", value = "密码(32位md5加密)", 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";
        }
    }
}
web_src/config/index.js
@@ -17,7 +17,8 @@
        pathRewrite: {
          '^/debug': '/'
        }
      }
      },
    },
    // Various Dev Server settings
web_src/src/components/DeviceList.vue
@@ -215,8 +215,8 @@
        console.log(`修改传输方式为 ${row.streamMode}:${row.deviceId} `);
        let that = this;
        this.$axios({
          method: 'get',
          url: '/api/device/query/transport' + row.deviceId + '/' + row.streamMode
          method: 'post',
          url: '/api/device/query/transport/' + row.deviceId + '/' + row.streamMode
        }).then(function(res) {
        }).catch(function(e) {
web_src/src/components/Login.vue
@@ -63,7 +63,7 @@
      this.$axios({
          method: 'get',
    url:"/api/user/login",
          url:"/api/user/login",
        params: loginParam
      }).then(function (res) {
        console.log(JSON.stringify(res));
@@ -81,7 +81,7 @@
              });
          }
      }).catch(function (error) {
        that.$message.error(error.response.statusText);
        that.$message.error(error.response.data.msg);
        that.isLoging = false;
      });
    },
web_src/src/components/UiHeader.vue
@@ -1,21 +1,30 @@
<template>
    <div id="UiHeader">
        <el-menu router :default-active="this.$route.path" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b" mode="horizontal">
        <el-menu router :default-active="this.$route.path" menu-trigger="click" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b" mode="horizontal">
            <el-menu-item index="/">控制台</el-menu-item>
            <el-menu-item index="/deviceList">设备列表</el-menu-item>
            <el-menu-item index="/pushVideoList">推流列表</el-menu-item>
            <el-menu-item index="/streamProxyList">拉流代理</el-menu-item>
            <el-menu-item index="/parentPlatformList/15/1">国标级联</el-menu-item>
            <el-menu-item @click="openDoc">在线文档</el-menu-item>
            <el-switch v-model="alarmNotify"  active-text="报警信息推送" style="display: block float: right" @change="sseControl"></el-switch>
            <el-menu-item style="float: right;" @click="loginout">退出</el-menu-item>
<!--            <el-menu-item style="float: right;" @click="loginout">退出</el-menu-item>-->
            <el-submenu index="" style="float: right;" >
              <template slot="title">欢迎,{{this.$cookies.get("session").username}}</template>
              <el-menu-item @click="changePassword">修改密码</el-menu-item>
              <el-menu-item @click="loginout">注销</el-menu-item>
            </el-submenu>
        </el-menu>
    <changePasswordDialog ref="changePasswordDialog"></changePasswordDialog>
    </div>
</template>
<script>
import changePasswordDialog from './dialog/changePassword.vue'
export default {
    name: "UiHeader",
    components: { Notification },
    components: { Notification, changePasswordDialog },
    data() {
        return {
            alarmNotify: true,
@@ -24,10 +33,25 @@
    },
    methods:{
          loginout(){
          this.$axios({
            method: 'get',
            url:"/api/user/logout"
          }).then((res)=> {
            // 删除cookie,回到登录页面
            this.$cookies.remove("session");
            this.$router.push('/login');
            this.sseSource.close();
          }).catch((error)=> {
            console.error("登出失败")
            console.error(error)
          });
        },
        changePassword(){
          this.$refs.changePasswordDialog.openDialog()
        },
        openDoc(){
            console.log(process.env.BASE_API)
          window.open( !!process.env.BASE_API? process.env.BASE_API + "/doc.html": "/doc.html")
        },
        beforeunloadHandler() {
            this.sseSource.close();
web_src/src/components/dialog/changePassword.vue
New file
@@ -0,0 +1,107 @@
<template>
  <div id="changePassword" v-loading="isLoging">
    <el-dialog
      title="修改密码"
      width="40%"
      top="2rem"
      :close-on-click-modal="false"
      :visible.sync="showDialog"
      :destroy-on-close="true"
      @close="close()"
    >
      <div id="shared" style="margin-right: 20px;">
        <el-form ref="passwordForm" :rules="rules" status-icon label-width="80px">
              <el-form-item label="新密码" prop="newPassword" >
                <el-input v-model="newPassword" autocomplete="off"></el-input>
              </el-form-item>
              <el-form-item label="确认密码" prop="confirmPassword">
                <el-input v-model="confirmPassword" autocomplete="off"></el-input>
              </el-form-item>
              <el-form-item>
                <div style="float: right;">
                  <el-button type="primary" @click="onSubmit">保存</el-button>
                  <el-button @click="close">取消</el-button>
                </div>
              </el-form-item>
            </el-form>
      </div>
    </el-dialog>
  </div>
</template>
<script>
export default {
  name: "changePassword",
  props: {},
  computed: {},
  created() {},
  data() {
    let validatePass = (rule, value, callback) => {
      if (value === '') {
        callback(new Error('请输入密码'));
      } else {
        if (this.confirmPassword !== '') {
          this.$refs.passwordForm.validateField('confirmPassword');
        }
        callback();
      }
    };
    let validatePass2 = (rule, value, callback) => {
      if (this.confirmPassword === '') {
        callback(new Error('请再次输入密码'));
      } else if (this.confirmPassword !== this.newPassword) {
        callback(new Error('两次输入密码不一致!'));
      } else {
        callback();
      }
    };
    return {
      newPassword: null,
      confirmPassword: null,
      showDialog: false,
      isLoging: false,
      rules: {
        newPassword: [{ required: true, validator: validatePass, trigger: "blur" }],
        confirmPassword: [{ required: true, validator: validatePass2, trigger: "blur" }],
      },
    };
  },
  methods: {
    openDialog: function () {
      this.showDialog = true;
    },
    onSubmit: function () {
      this.$axios({
        method: 'post',
        url:"/api/user/changePassword",
        params: {
          password: this.newPassword
        }
      }).then((res)=> {
        if (res.data === "success"){
          this.$message({
            showClose: true,
            message: '修改成功,请重新登陆',
            type: 'success'
          });
          this.showDialog = false;
          setTimeout(()=>{
            // 删除cookie,回到登录页面
            this.$cookies.remove("session");
            this.$router.push('/login');
            this.sseSource.close();
          },800)
        }
      }).catch((error)=> {
        console.error(error)
      });
    },
    close: function () {
      this.showDialog = false;
      this.newPassword= null;
      this.confirmPassword=null;
    },
  },
};
</script>
web_src/src/main.js
@@ -40,6 +40,18 @@
axios.defaults.baseURL = (process.env.NODE_ENV === 'development') ? process.env.BASE_API : "";
// api 返回401自动回登陆页面
axios.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  return response;
}, function (error) {
  // 对响应错误做点什么
  if (error.response.status === 401) {
    router.push('/login');
  }
  return Promise.reject(error);
});
Vue.prototype.$cookies.config(60*30);