Ver código fonte

feat: spring security

考虑以后再加入jwt
370774330@qq.com 5 anos atrás
pai
commit
bea65aa7af

+ 17 - 5
web/pom.xml

@@ -80,10 +80,15 @@
             <artifactId>mybatis-spring-boot-starter-test</artifactId>
             <version>2.1.4</version>
         </dependency>
-<!--        <dependency>-->
-<!--            <groupId>org.springframework.boot</groupId>-->
-<!--            <artifactId>spring-boot-starter-security</artifactId>-->
-<!--        </dependency>-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-test</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
@@ -101,7 +106,14 @@
             </dependency>
         </dependencies>
     </dependencyManagement>
-
+    <repositories>
+        <!-- ... possibly other repository elements ... -->
+        <repository>
+            <id>spring-snapshot</id>
+            <name>Spring Snapshot Repository</name>
+            <url>https://repo.spring.io/snapshot</url>
+        </repository>
+    </repositories>
     <build>
         <plugins>
             <plugin>

+ 1 - 1
web/src/main/java/seecoder/devcloud/web/controller/user/UserController.java

@@ -39,7 +39,7 @@ public class UserController {
         return Response.ok();
     }
 
-    @PutMapping("nickname")
+    @PutMapping("/nickname")
     public Response<UserVO> changeUserInfo(
             @RequestParam("userId") Integer userid,
             @RequestParam("nickname")

+ 6 - 1
web/src/main/java/seecoder/devcloud/web/enums/UserIdentity.java

@@ -8,7 +8,12 @@ public enum UserIdentity {
 	/**
 	 * 教师角色
 	 */
-	TEACHER("教师");
+	TEACHER("教师"),
+
+	/**
+	 * 管理员角色
+	 */
+	ADMIN("管理员");
 
 	private final String name;
 

+ 81 - 0
web/src/main/java/seecoder/devcloud/web/infrastructure/config/WebSecurityConfig.java

@@ -0,0 +1,81 @@
+package seecoder.devcloud.web.infrastructure.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.DependsOn;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+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.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.web.cors.CorsUtils;
+import seecoder.devcloud.web.infrastructure.security.WebSecurityConstants;
+import seecoder.devcloud.web.service.impl.user.UserServiceImpl;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.swing.*;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/3/5
+ * @description:
+ */
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)  //  启用方法级别的权限认证
+@DependsOn("userServiceImpl")
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+
+    @Qualifier("userServiceImpl")
+    @Autowired
+    private UserDetailsService userDetailsService;
+
+    @Autowired
+    private PasswordEncoder passwordEncoder;
+
+
+    @Override
+    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+        auth.userDetailsService(userDetailsService)
+                .passwordEncoder(passwordEncoder);
+    }
+
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http
+
+                .authorizeRequests()
+
+                //跨域的Options请求进行放行
+                .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
+                //todo 具体角色权限配置等开发完成之后再配, 这里先配为默认所有权限
+                .antMatchers("/**").hasAnyRole(WebSecurityConstants.STUDENT_ROLE,WebSecurityConstants.TEACHER_ROLE,WebSecurityConstants.ADMIN_ROLE)
+                .anyRequest().authenticated()
+                .and()
+                .formLogin()
+                .and()
+                .logout()
+                .and()
+                .httpBasic();
+
+        //csrf保护,postman本地测试的时候要么disable,要么带上token参数
+        //todo spring security
+        http.csrf();
+    }
+
+
+
+}

+ 22 - 0
web/src/main/java/seecoder/devcloud/web/infrastructure/security/WebSecurityBean.java

@@ -0,0 +1,22 @@
+package seecoder.devcloud.web.infrastructure.security;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+/**
+ * @author PuHong Weng
+ * @date 2021/3/6
+ * @description:
+ */
+@Configuration
+public class WebSecurityBean {
+
+    @Bean
+    public PasswordEncoder passwordEncoder(){
+        return new BCryptPasswordEncoder();
+    }
+}
+

+ 19 - 1
web/src/main/java/seecoder/devcloud/web/infrastructure/security/WebSecurityConstants.java

@@ -2,6 +2,10 @@ package seecoder.devcloud.web.infrastructure.security;
 
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import seecoder.devcloud.web.enums.UserIdentity;
+
+import java.util.HashMap;
+import java.util.Map;
 
 public abstract class WebSecurityConstants {
 	private static final String ROLE_PREFIX = "ROLE_";
@@ -9,8 +13,22 @@ public abstract class WebSecurityConstants {
 	public static final String STUDENT_ROLE = "STUDENT";
 	public static final String TEACHER_ROLE = "TEACHER";
 	public static final String ADMIN_ROLE = "ADMIN";
-
+	
 	public static final GrantedAuthority STUDENT_AUTHORITY = new SimpleGrantedAuthority(ROLE_PREFIX + STUDENT_ROLE);
 	public static final GrantedAuthority TEACHER_AUTHORITY = new SimpleGrantedAuthority(ROLE_PREFIX + TEACHER_ROLE);
 	public static final GrantedAuthority ADMIN_AUTHORITY = new SimpleGrantedAuthority(ROLE_PREFIX + ADMIN_ROLE);
+
+
+
+	private static Map<UserIdentity,GrantedAuthority> authorityTable= new HashMap<>();
+	
+	static {
+		authorityTable.put(UserIdentity.STUDENT, STUDENT_AUTHORITY);
+		authorityTable.put(UserIdentity.TEACHER, TEACHER_AUTHORITY);
+		authorityTable.put(UserIdentity.ADMIN, ADMIN_AUTHORITY);
+	}
+	
+	public static GrantedAuthority getAuthority(UserIdentity identity){
+		return authorityTable.get(identity);
+	}
 }

+ 33 - 1
web/src/main/java/seecoder/devcloud/web/po/user/User.java

@@ -11,16 +11,21 @@ import org.hibernate.annotations.LazyCollection;
 import org.hibernate.annotations.LazyCollectionOption;
 import org.springframework.data.annotation.CreatedDate;
 import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
 import seecoder.devcloud.web.enums.UserIdentity;
+import seecoder.devcloud.web.infrastructure.security.WebSecurityConstants;
 
 import javax.persistence.*;
 import java.sql.Timestamp;
 import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
 @Data
-public class User {
+public class User implements UserDetails {
 
 	/**
 	 * 注意用户id并非由此后端数据库生成,而是gitlab创建后填入,以确保两者的关联性的正确
@@ -50,4 +55,31 @@ public class User {
 	 * 这个属性来自于gitlab
 	 */
 	private String token;
+
+	@Override
+	public Collection<? extends GrantedAuthority> getAuthorities() {
+		return Collections.singleton(WebSecurityConstants.getAuthority(role));
+	}
+
+	// spring security以下四个方法目前没有需求,默认全部返回true
+
+	@Override
+	public boolean isAccountNonExpired() {
+		return true;
+	}
+
+	@Override
+	public boolean isAccountNonLocked() {
+		return true;
+	}
+
+	@Override
+	public boolean isCredentialsNonExpired() {
+		return true;
+	}
+
+	@Override
+	public boolean isEnabled() {
+		return true;
+	}
 }

+ 0 - 1
web/src/main/java/seecoder/devcloud/web/service/impl/user/GroupServiceImpl.java

@@ -4,7 +4,6 @@ package seecoder.devcloud.web.service.impl.user;
 import lombok.extern.slf4j.Slf4j;
 import org.gitlab4j.api.GitLabApiException;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.ApplicationContext;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import seecoder.devcloud.api.gitlab.GitlabApi;

+ 27 - 9
web/src/main/java/seecoder/devcloud/web/service/impl/user/UserServiceImpl.java

@@ -6,7 +6,12 @@ import org.apache.commons.codec.digest.DigestUtils;
 import org.gitlab4j.api.GitLabApiException;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.DependsOn;
 import org.springframework.data.domain.Sort;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.Assert;
@@ -29,20 +34,25 @@ import seecoder.devcloud.web.service.user.UserService;
 
 @Service
 @Slf4j
-public class UserServiceImpl implements UserService {
+public class UserServiceImpl implements UserService, UserDetailsService {
 	private static final Sort PAGE_SORT = Sort.by(Sort.Direction.ASC, "id");
 
 	private final UserMapper userMapper;
 	private final MailService mailService;
 	private final GitlabApi gitlabApi;
 	private final JenkinsApi jenkinsApi;
+	/**
+	 * spring security提供的加密
+	 */
+	private final PasswordEncoder passwordEncoder;
 
 	@Autowired
-	public UserServiceImpl(UserMapper userMapper, MailService mailService, GitlabApi gitlabApi, JenkinsApi jenkinsApi) {
+	public UserServiceImpl(UserMapper userMapper, MailService mailService, GitlabApi gitlabApi, JenkinsApi jenkinsApi, PasswordEncoder passwordEncoder) {
 		this.userMapper = userMapper;
 		this.mailService = mailService;
 		this.gitlabApi = gitlabApi;
 		this.jenkinsApi = jenkinsApi;
+		this.passwordEncoder = passwordEncoder;
 	}
 
 	@Override
@@ -79,7 +89,7 @@ public class UserServiceImpl implements UserService {
 		user.setUsername(username);
 		user.setEmail(email);
 		//在gitlab注册后,直接加密
-		user.setPassword(DigestUtils.sha256Hex(realPassword));
+		user.setPassword(passwordEncoder.encode(realPassword));
 		user.setRole(role);
 		userMapper.insert(user, "created_at");
 		final UserVO userVO = new UserVO(user);
@@ -125,13 +135,13 @@ public class UserServiceImpl implements UserService {
 	public void changePassword(ChangePasswordVO form) throws ServiceException {
 		User user = userMapper.findById(form.getUserId());
 
-		if (!user.getPassword().equals(DigestUtils.sha256Hex(form.getOrigin()))) {
+		if (!passwordEncoder.matches(form.getOrigin(),user.getPassword())) {
 			throw new InvalidRequestException("原密码验证失败,如忘记密码请联系管理员重置");
 		}
-		changePassword(user, form.getPassword(), false);
+		changePassword(user, form.getPassword());
 	}
 
-	private void changePassword(User user, String password, boolean reset) throws ServiceException {
+	private void changePassword(User user, String password) throws ServiceException {
 		try {
 			/**
 			 * todo 修改密码容易导致gitlab与devcloud不一致
@@ -146,10 +156,18 @@ public class UserServiceImpl implements UserService {
 			log.error(e.getMessage(), e);
 			throw new ServiceException("修改GitLab用户密码时发生异常[" + e.getMessage() + "]");
 		}
-		user.setPassword(DigestUtils.sha256Hex(password));
+		user.setPassword(passwordEncoder.encode(password));
 		userMapper.updateById(user);
-		if (reset) {
-			mailService.send( "您的新密码为"+password,"密码变更提醒 | SEECODER",user.getEmail());
+	}
+
+	@Override
+	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+		UserDetails u = userMapper.findByUsername(username);
+		if (u == null){
+			throw new UsernameNotFoundException("此账号不存在");
 		}
+		return u;
 	}
+
+
 }

+ 1 - 1
web/src/test/java/seecoder/devcloud/web/service/user/GroupServiceTest.java

@@ -55,7 +55,7 @@ class GroupServiceTest {
     }
 
     @Test
-    void handler(){
+    void help(){
         try {
             //gitLabApi.getGroupApi().deleteGroup(46);
             System.out.println(gitLabApi.getGroupApi().getGroups());

+ 1 - 2
web/src/test/java/seecoder/devcloud/web/service/user/UserServiceTest.java

@@ -26,7 +26,6 @@ import java.util.stream.Collectors;
  */
 @RunWith(SpringRunner.class)
 @SpringBootTest
-@ComponentScan({"seecoder.devcloud.api","seecoder.devcloud.common","seecoder.devcloud.web"})
 @MapperScan(basePackages = {"seecoder.devcloud.web.dao"})
 class UserServiceTest {
 
@@ -67,7 +66,7 @@ class UserServiceTest {
     @Test
     void delete() {
         try {
-            userService.delete(36);
+            userService.delete(37);
             //gitLabApi.getUserApi().deleteUser(35);
             System.out.println(gitLabApi.getUserApi().getActiveUsers().stream().map(AbstractUser::getId).collect(Collectors.toList()));
         } catch (Exception e) {