Browse Source

21/2/8 增加security的权限校验案例

smile 4 years ago
parent
commit
ed71d89bb2
19 changed files with 751 additions and 84 deletions
  1. 113 84
      common-springboot/pom.xml
  2. 157 0
      common-springboot/src/main/java/com/gihon/security/config/MySecurityConfiguration.java
  3. 29 0
      common-springboot/src/main/java/com/gihon/security/controller/DispatcherController.java
  4. 46 0
      common-springboot/src/main/java/com/gihon/security/controller/UserController.java
  5. 51 0
      common-springboot/src/main/java/com/gihon/security/encoder/MyPasswordEncoder.java
  6. 22 0
      common-springboot/src/main/java/com/gihon/security/handler/MyAccessDeniedHandler.java
  7. 20 0
      common-springboot/src/main/java/com/gihon/security/handler/MyFailureHandler.java
  8. 22 0
      common-springboot/src/main/java/com/gihon/security/handler/MySuccessHandler.java
  9. 22 0
      common-springboot/src/main/java/com/gihon/security/mapper/UserMapper.java
  10. 18 0
      common-springboot/src/main/java/com/gihon/security/matchers/MyPermissionsMatcher.java
  11. 28 0
      common-springboot/src/main/java/com/gihon/security/matchers/impl/MyPermissionsMatcherImpl.java
  12. 22 0
      common-springboot/src/main/java/com/gihon/security/pojo/User.java
  13. 69 0
      common-springboot/src/main/java/com/gihon/security/service/impl/UserLoginServiceImpl.java
  14. 63 0
      common-springboot/src/main/resources/application.yml
  15. 3 0
      common-springboot/src/main/resources/static/css/index.css
  16. 1 0
      common-springboot/src/main/resources/static/js/index.js
  17. 10 0
      common-springboot/src/main/resources/templates/fail.html
  18. 26 0
      common-springboot/src/main/resources/templates/login.html
  19. 29 0
      common-springboot/src/main/resources/templates/success.html

+ 113 - 84
common-springboot/pom.xml

@@ -1,92 +1,121 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
-	<modelVersion>4.0.0</modelVersion>
-	<parent>
-		<groupId>gihon.common</groupId>
-		<artifactId>common-parent</artifactId>
-		<version>0.0.1-SNAPSHOT</version>
-	</parent>
-	<artifactId>common-springboot</artifactId>
-	<name>common-springboot</name>
-	<description>Demo project for Spring Boot</description>
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>gihon.common</groupId>
+        <artifactId>common-parent</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+    <artifactId>common-springboot</artifactId>
+    <name>common-springboot</name>
+    <description>Demo project for Spring Boot</description>
 
 
-	<dependencies>
-		<dependency>
-			<groupId>gihon.common</groupId>
-			<artifactId>common-util</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.springframework.boot</groupId>
-			<artifactId>spring-boot-starter-jdbc</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.springframework.boot</groupId>
-			<artifactId>spring-boot-starter-quartz</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.springframework.boot</groupId>
-			<artifactId>spring-boot-starter-validation</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.springframework.boot</groupId>
-			<artifactId>spring-boot-starter-web</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.mybatis.spring.boot</groupId>
-			<artifactId>mybatis-spring-boot-starter</artifactId>
-		</dependency>
+    <dependencies>
+        <dependency>
+            <groupId>gihon.common</groupId>
+            <artifactId>common-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-jdbc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-quartz</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+        </dependency>
 
-		<dependency>
-			<groupId>org.springframework.boot</groupId>
-			<artifactId>spring-boot-devtools</artifactId>
-			<scope>runtime</scope>
-			<optional>true</optional>
-		</dependency>
-		<dependency>
-			<groupId>com.h2database</groupId>
-			<artifactId>h2</artifactId>
-			<scope>runtime</scope>
-		</dependency>
-		<dependency>
-			<groupId>com.oracle.database.jdbc</groupId>
-			<artifactId>ojdbc8</artifactId>
-			<scope>runtime</scope>
-		</dependency>
-		<dependency>
-			<groupId>mysql</groupId>
-			<artifactId>mysql-connector-java</artifactId>
-			<scope>runtime</scope>
-		</dependency>
-		<dependency>
-			<groupId>org.mybatis.spring.boot</groupId>
-			<artifactId>mybatis-spring-boot-starter</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>com.baomidou</groupId>
-			<artifactId>mybatis-plus-boot-starter</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.springframework.boot</groupId>
-			<artifactId>spring-boot-starter-test</artifactId>
-			<scope>test</scope>
-			<exclusions>
-				<exclusion>
-					<groupId>org.junit.vintage</groupId>
-					<artifactId>junit-vintage-engine</artifactId>
-				</exclusion>
-			</exclusions>
-		</dependency>
-	</dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <scope>runtime</scope>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.oracle.database.jdbc</groupId>
+            <artifactId>ojdbc8</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
 
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>org.springframework.boot</groupId>
-				<artifactId>spring-boot-maven-plugin</artifactId>
-			</plugin>
-		</plugins>
-	</build>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.junit.vintage</groupId>
+                    <artifactId>junit-vintage-engine</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!--security-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-thymeleaf</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.thymeleaf.extras</groupId>
+            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
+            <version>3.0.4.RELEASE</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+        <!--security  end-->
+        <!--单点登录-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.session</groupId>
+            <artifactId>spring-session-data-redis</artifactId>
+        </dependency>
+
+    </dependencies>
+
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
 
 </project>

+ 157 - 0
common-springboot/src/main/java/com/gihon/security/config/MySecurityConfiguration.java

@@ -0,0 +1,157 @@
+package com.gihon.security.config;
+
+import com.gihon.security.encoder.MyPasswordEncoder;
+import com.gihon.security.handler.MyAccessDeniedHandler;
+import com.gihon.security.handler.MyFailureHandler;
+import com.gihon.security.handler.MySuccessHandler;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+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.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
+import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
+
+import javax.sql.DataSource;
+
+/**
+ * SpringSecurity的配置类型
+ * 继承父类型WebSecurityConfigurerAdapter
+ * WebSecurityConfigurerAdapter是一个Security提供的配置模板,可以快速实现
+ * Security配置,简化自定义配置的复杂度。
+ *
+ * SpringSecurity提供的默认环境,如:登录页面、登录处理请求(/login)、拦截处理(未登录访问其他地址自动跳转到登录页面)
+ * 只有在不提供自定义Security配置的时候,才有效。
+ *
+ * Security要求自定义的UserDetailsService配置配套提供一个PasswordEncoder类型的对象,
+ * 对象中提供密码的加密和匹配逻辑,为认证提供加密处理。
+ * 如果spring环境中没有PasswordEncoder类型的对象,UserDetailsService类型的对象无效。
+ */
+@Configuration
+public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
+    /**
+     * 在Security中,处理认证逻辑的具体代码,不需要提供控制器逻辑。
+     * 因为Security根据配置formLogin().loginProcessingUrl()自动做控制跳转。
+     * 跳转逻辑是:
+     *  找UserDetailsService接口的实现类型对象,调用启动的方法loadUserByUsername,实现登录认证。
+     * @param http
+     * @throws Exception
+     */
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        // 设置登录页面路径地址是 '/'
+        http.formLogin()   // 获取Security中专门用来配置登录相关逻辑的配置类型
+                .usernameParameter("name") // 设置请求参数中,用户名的参数名称
+                .passwordParameter("pswd") // 设置请求参数中,密码的参数名称
+                .loginPage("/")  // 配置登录页面请求地址是什么。
+                .loginProcessingUrl("/login") // 使用POST访问,访问什么地址的时候,实现认证逻辑。登录地址。
+                // 认证成功或失败,Security会转发请求,到指定的地址。转发的请求是POST方式的。需要定义控制器处理。
+                // .successForwardUrl("/success") // 登录成功后,跳转地址。 请求转发, 重复登录。
+                // .defaultSuccessUrl("/success") // 登录成功后,重定向到指定位置
+                .successHandler(new MySuccessHandler("/sso/success")) // 登录成功后,自定义处理逻辑。
+                // .failureForwardUrl("/fail"); // 登录失败后,跳转地址
+                // .failureUrl("/fail"); // 登录失败后,响应重定向
+                .failureHandler(new MyFailureHandler("/sso/fail")); // 登录失败后,自定义处理逻辑
+
+        /**
+         * 设置权限相关
+         * 可以通过对ExpressionUrlAuthorizationConfigurer配置,实现对URL地址的权限管理。
+         * 可以定义针对固定地址、表达式地址的权限控制。
+         * 常用权限控制方法:
+         *  1、 permitAll() - 全权限访问,任意客户端都可访问。
+         *  2、 authenticated() - 必须认证后才能访问。登录后才能访问。
+         *  3、 denyAll() - 无访问权限。 没有登录的时候,要求登录;登录后,显示403无访问权限错误。
+         *  4、 anonymous() - 匿名访问。可以不登录就访问的资源。和permitAll类似。
+         *  5、 rememberMe() - 记住我。当认证的时候,开启了rememberMe功能,可以使用记住我访问。
+         *  6、 fullyAuthenticated() - 完整认证才可访问。 rememberMe不能访问。
+         *
+         * 联合使用:
+         *  如:  antMatchers("/", "/index").permitAll(); 请求 "/" 或 "/index" 的时候,授予所有权限。
+         * 基于角色和权限的访问控制:
+         *  必须建立在已认证的基础上,才会执行对应的访问控制。
+         *  1、 基于角色的访问控制:
+         *      URL匹配().hasRole | hasAnyRole
+         *      hasRole(String role) - 判断已登录的用户权限集合中,是否包含参数角色
+         *        底层是调用的access("hasRole('ROLE_"+role+"')")
+         *      hasAnyRole(String... roles) - 判断已登录的用户权限集合中,是否包括参数中的任意角色。
+         *        底层是调用的access("hasAnyRole('ROLE_角色1','ROLE_角色2'....)")
+         *  2、 基于权限的访问控制:
+         *      hasAuthority(String authority) - 判断已登录的用户权限集合中,是否包含权限
+         *        底层: access("hasAuthority('权限')")
+         *      hasAnyAuthority(String... authorities) - 判断已登录的用户权限集合中,是否包含参数中任意权限
+         *        底层: access("hasAnyAuthority('权限1','权限2'....)")
+         *  3、 基于IP的访问控制:
+         *      hasIpAddress(String ipAddress) 检查客户端IP是否是参数指定的IP
+         *      客户端IP是: HttpServletRequest.getRemoteAddr()方法的返回值。
+         *      底层: access("hasIpAddress('IP地址')")
+         */
+        http.authorizeRequests() // 获取Security中专门配置权限管理的配置类型
+                .antMatchers("/", "/fail").permitAll() // 访问 '/' 地址的时候,不需要登录。
+                .antMatchers("/**/*.js").permitAll() // 只处理js文件
+                .regexMatchers(".+[.]css").permitAll() // 只处理css
+//                .antMatchers("/anonymous").anonymous() // 匿名访问
+//                .antMatchers("/denyAll").denyAll() // 不能访问
+//                .antMatchers("/hasAdminRole").hasRole("管理员") // 判断有没有"管理员"角色
+//                .antMatchers("/hasUserRole").hasRole("普通用户") // 判断有没有"普通用户"角色
+//                .antMatchers("/hasAnyRole").hasAnyRole("管理员", "普通用户") // 判断是否包含管理员或普通用户角色。
+//                .antMatchers("/hasAuthority").hasAuthority("userManagement")
+//                .antMatchers("/hasAnyAuthority").hasAnyAuthority("userManagement","selfManagement")
+//                .antMatchers("/accessRole").access("hasRole('ROLE_管理员')")// 使用access,角色控制
+//                .antMatchers("/accessAuthority").access("hasAuthority('selfManagement')")// 使用access,权限控制
+                // WebSecurityExpressionRoot
+//                .antMatchers("/*").access("@myPermissionsMatcherImpl.hasPermission(request,authentication)")
+                .anyRequest().authenticated(); // 所有的请求地址,都必须登录才能访问。
+
+        // 异常处理配置
+        http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());
+
+        // 配置rememberMe, 使用rememberMe功能的时候,认证成功结果跳转,一定要使用重定向。
+        http.rememberMe()
+                .rememberMeParameter("rememberMe") // 设置请求参数名,默认为remember-me
+                .rememberMeCookieName("rememberMe") // 设置记住我的cookie名称。默认为remember-me
+                .tokenValiditySeconds(300) // 设置rememberMe有效时长。默认14天。
+                .userDetailsService(userLoginServiceImpl) // 设置用户认证逻辑对象
+                .tokenRepository(tokenRepository); // 设置rememberMe数据持久化对象。持久化用户认证成功后的主体。
+
+        // 退出配置, 注销配置
+        http.logout()
+                .invalidateHttpSession(true) // 退出的时候,销毁HttpSession对象。默认true
+                .clearAuthentication(true) // 退出的时候,清空已认证用户主体对象。默认true
+                .logoutUrl("/logout")  // 退出登录的请求地址。默认是 /logout
+                .logoutSuccessUrl("/"); // 退出后的跳转地址,重定向。 默认是 loginPage?logout
+
+        // csrf安全处理, 关闭csrf。 防止恶意攻击的技术。 security4+ 默认开启的。
+        // http.csrf().disable();
+    }
+
+//    @Autowired
+//    private DataSource dataSource;
+
+    // rememberMe数据保存对象,相当于是DAO
+    @Bean
+    public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){
+        // 使用Spring-JDBC技术,实现rememberMe数据的存储。保存到数据库。
+        // 在JdbcTokenRepositoryImpl类型的对象中,必须提供一个DataSource对象。
+        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
+        jdbcTokenRepository.setDataSource(dataSource);
+        // rememberMe表格的创建, 只能在系统第一次启动的时候创建。 默认是false,不会启动创建表格。
+        // jdbcTokenRepository.setCreateTableOnStartup(true);
+
+        return jdbcTokenRepository;
+    }
+
+    @Autowired
+    private UserDetailsService userLoginServiceImpl;
+
+    @Autowired
+    private PersistentTokenRepository tokenRepository;
+
+    // 传递的构造参数BCryptPasswordEncoder(int strength), 建议为8的整数倍。
+    @Bean
+    public PasswordEncoder passwordEncoder(){
+        return new MyPasswordEncoder();
+    }
+}

+ 29 - 0
common-springboot/src/main/java/com/gihon/security/controller/DispatcherController.java

@@ -0,0 +1,29 @@
+package com.gihon.security.controller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * 视图跳转控制器
+ */
+@Controller
+public class DispatcherController {
+    @RequestMapping(value={"/fail"}, method = {RequestMethod.GET, RequestMethod.POST})
+    public String toFail(){
+        System.out.println("fail");
+        return "/fail";
+    }
+
+    @RequestMapping(value={"/success"}, method = {RequestMethod.GET, RequestMethod.POST})
+    public String toSuccess(){
+        return "success";
+    }
+
+    // 跳转到登录页面
+    @GetMapping("/")
+    public String toLogin(){
+        return "login";
+    }
+}

+ 46 - 0
common-springboot/src/main/java/com/gihon/security/controller/UserController.java

@@ -0,0 +1,46 @@
+package com.gihon.security.controller;
+
+import org.springframework.security.access.annotation.Secured;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 用户控制器
+ * SpringSecurity中关于权限管理的注解,默认不生效。
+ * 必须通过注解EnableGlobalMethodSecurity来开启,才能生效。
+ * EnableGlobalMethodSecurity注解一般都定义在启动类上。也可以定义在,任意的、启动类可以扫描到的类上。
+ */
+@RestController
+public class UserController {
+    /**
+     * PreAuthorize - 前置权限判断注解。注解描述的方法执行前判断认证用户是否包含权限。
+     * PostAuthorize - 后置权限判断。注解描述的方法, 执行后判断认证用户是否包含权限。
+     * @return
+     */
+    @RequestMapping("/preAuthorize")
+    @PreAuthorize(value = "hasAuthority('userManagement')")
+    public String preAuthorize(){
+        return "前置权限判断。";
+    }
+
+    /**
+     * 查看用户
+     * Secured注解,相当于是定义了, antMatchers("/queryUser").hasAnyRole("管理员","超级管理员");
+     * @return
+     */
+    @Secured(value = {"ROLE_管理员","ROLE_超级管理员"})
+    @RequestMapping("/queryUser")
+    public String queryUser(){
+        return "queryUser";
+    }
+    /**
+     * 增加用户
+     */
+    @Secured(value = {"ROLE_管理员","ROLE_超级管理员"})
+    @RequestMapping("/addUser")
+    public String addUser(){
+
+        return "queryUser";
+    }
+}

+ 51 - 0
common-springboot/src/main/java/com/gihon/security/encoder/MyPasswordEncoder.java

@@ -0,0 +1,51 @@
+package com.gihon.security.encoder;
+
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import java.security.MessageDigest;
+
+/**
+ * 自定义密码加密器
+ */
+// @Component
+public class MyPasswordEncoder implements PasswordEncoder {
+    // 加密方法,把请求参数提供的password数据,加密。返回加密后的字符串
+    // 使用MD5传统加密。 16字节加密方式。
+    @Override
+    public String encode(CharSequence charSequence) {
+        try {
+            MessageDigest digest = MessageDigest.getInstance("MD5");
+            // MD5加密算法,加密后的返回结果一定是16长度的字节数组。
+            byte[] tmps = digest.digest(charSequence.toString().getBytes());
+            StringBuilder builder = new StringBuilder("");
+            //System.out.println("加密后的结果数组长度:" + tmps.length);
+            for(byte tmp : tmps){
+                String s = Integer.toHexString(tmp & 0xFF);
+                if(s.length() == 1){
+                    builder.append("0");
+                }
+                builder.append(s);
+            }
+            System.out.println(builder.toString());
+            return builder.toString();
+        }catch (Exception e){
+            return null;
+        }
+
+        // 没有任何加密。就是原字符串
+        // return charSequence.toString();
+    }
+
+    // 比较用户的真实密码和参数传递密码是否匹配。
+    // 真实密码存在数据库中,是加密后的数据,encode方法的返回值。
+    // 请求参数密码是加密前的数据,需要加密后和真实密码比较。
+    // 参数charSequence - 原密码,请求参数传递的密码
+    // 参数s - 真实密码,就是数据库中记录的密码。
+    @Override
+    public boolean matches(CharSequence charSequence, String s) {
+        System.out.println(charSequence);
+        System.out.println(s);
+
+        return s.equals(encode(charSequence));
+    }
+}

+ 22 - 0
common-springboot/src/main/java/com/gihon/security/handler/MyAccessDeniedHandler.java

@@ -0,0 +1,22 @@
+package com.gihon.security.handler;
+
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public class MyAccessDeniedHandler implements AccessDeniedHandler {
+    // 当用户无权限的时候,如何处理。
+    // 通过响应,向客户端输出HTML,通用用户无权限,可以联系管理员。
+    @Override
+    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
+        response.setContentType("text/html;charset=UTF-8");
+        // 响应状态码, 403, 无权限
+        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+        response.getWriter().println("<html><body style='text-align: center;'><h3>无权限,请联系管理员!</h3></body></html>");
+        response.getWriter().flush();
+    }
+}

+ 20 - 0
common-springboot/src/main/java/com/gihon/security/handler/MyFailureHandler.java

@@ -0,0 +1,20 @@
+package com.gihon.security.handler;
+
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public class MyFailureHandler implements AuthenticationFailureHandler {
+    private String url;
+    public MyFailureHandler(String url){
+        this.url = url;
+    }
+    @Override
+    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
+        httpServletResponse.sendRedirect(url);
+    }
+}

+ 22 - 0
common-springboot/src/main/java/com/gihon/security/handler/MySuccessHandler.java

@@ -0,0 +1,22 @@
+package com.gihon.security.handler;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public class MySuccessHandler implements AuthenticationSuccessHandler {
+    private String url;
+    public MySuccessHandler(String url){
+        this.url = url;
+    }
+    @Override
+    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
+                                        HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
+        System.out.println("自定义响应重定向Handler处理器执行。");
+        httpServletResponse.sendRedirect(url);
+    }
+}

+ 22 - 0
common-springboot/src/main/java/com/gihon/security/mapper/UserMapper.java

@@ -0,0 +1,22 @@
+package com.gihon.security.mapper;
+
+import com.gihon.security.pojo.User;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+// 数据访问接口
+@Mapper
+public interface UserMapper {
+    @Select("select id, username, password from tb_user where username = #{username}")
+    User selectByUsername(String username);
+    // 根据用户的主键,查询角色的名称集合。
+    @Select("select name from tb_role where id in ( select role_id from tb_user_role where user_id = #{userId} )")
+    List<String> selectRoleNamesByUserId(Long userId);
+    // 根据用户的主键,查询权限的描述集合。
+    @Select("select permission from tb_permission where id in ( select perm_id from tb_role_perm where role_id in ( select role_id from tb_user_role where user_id = #{userId} ) )")
+    List<String> selectPermissionsByUserId(Long userId);
+
+
+}

+ 18 - 0
common-springboot/src/main/java/com/gihon/security/matchers/MyPermissionsMatcher.java

@@ -0,0 +1,18 @@
+package com.gihon.security.matchers;
+
+import org.springframework.security.core.Authentication;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 自定义权限管理控制
+ */
+public interface MyPermissionsMatcher {
+    /**
+     * 判断用户是否有当前请求地址的访问权限。
+     * @param request 用于获取访问地址的请求对象 request.getXxx方法获取请求地址
+     * @param authentication SpringSecurity中认证成功后,用户的主体对象。
+     * @return
+     */
+    boolean hasPermission(HttpServletRequest request, Authentication authentication);
+}

+ 28 - 0
common-springboot/src/main/java/com/gihon/security/matchers/impl/MyPermissionsMatcherImpl.java

@@ -0,0 +1,28 @@
+package com.gihon.security.matchers.impl;
+
+import com.gihon.security.matchers.MyPermissionsMatcher;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Collection;
+
+@Component
+public class MyPermissionsMatcherImpl implements MyPermissionsMatcher {
+    @Override
+    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
+        String servletPath = request.getServletPath();
+        System.out.println("本次请求的地址是:" + servletPath);
+        // 当前登录用户的权限集合
+        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
+
+        // 定义请求地址和权限描述一致。
+        GrantedAuthority currentAuthority = new SimpleGrantedAuthority(servletPath.substring(1));
+        //用户权限集合中是否包含当前的权限对象
+        boolean isContains = authorities.contains(currentAuthority);
+        System.out.println("用户权限集合中是否包含当前的权限对象:" + isContains);
+        return isContains;
+    }
+}

+ 22 - 0
common-springboot/src/main/java/com/gihon/security/pojo/User.java

@@ -0,0 +1,22 @@
+package com.gihon.security.pojo;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+import java.io.Serializable;
+
+/**
+ * 自定义实体类型,一定要和SpringSecurity提供的User类型区分开。
+ * 自定义的实体中,经常有User类型。
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode
+@ToString
+public class User implements Serializable {
+    private Long id;
+    private String username;
+    private String password;
+}

+ 69 - 0
common-springboot/src/main/java/com/gihon/security/service/impl/UserLoginServiceImpl.java

@@ -0,0 +1,69 @@
+package com.gihon.security.service.impl;
+
+import com.gihon.security.mapper.UserMapper;
+import com.gihon.security.pojo.User;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+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;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 自定义登录逻辑实现。
+ */
+@Service
+public class UserLoginServiceImpl implements UserDetailsService {
+    @Resource
+    private UserMapper userMapper;
+
+    // 登录逻辑,访问数据库实现。
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+        System.out.println("开始登陆  验证逻辑");
+        // 1、 访问数据库,根据用户名查询用户。
+        User user = userMapper.selectByUsername(username);
+
+        // 2、 判断查询结果是否存在, 决定是否需要抛出异常。
+        if(null == user){
+            // 用户不存在,抛出异常。
+            throw new UsernameNotFoundException("用户不存在");
+        }
+
+        // 3、 查询认证用户的权限集合。并封装成SpringSecurity需要的集合。List<GrantedAuthority>
+        // 用户的权限包括,角色和权限, 就是role和permission
+        // SpringSecurity中,要求角色的字符串描述信息,必须使用"ROLE_"作为前缀
+        // SpringSecurity中,对权限的字符串描述信息,没有强制要求。
+        // 3.1 查询登录用户的角色名集合
+        List<String> roleNames = userMapper.selectRoleNamesByUserId(user.getId());
+        // 3.2 查询登录用户的权限描述集合。就是tb_permission表格中permission字段的集合。
+        List<String> permissions = userMapper.selectPermissionsByUserId(user.getId());
+        // 3.3 封装一个List<GrantedAuthority>类型的集合
+        List<GrantedAuthority> grantedAuthorities = new ArrayList<>(roleNames.size()+permissions.size());
+        // 把角色保存到权限集合中
+        for(String roleName : roleNames){
+            // 角色字符串描述必须使用"ROLE_"开头
+            roleName = "ROLE_" + roleName;
+            grantedAuthorities.add(new SimpleGrantedAuthority(roleName));
+        }
+        for(String perm : permissions){
+            // 权限的集合没有特殊要求
+            grantedAuthorities.add(new SimpleGrantedAuthority(perm));
+        }
+
+        // 4、 返回UserDetails接口的实现对象, SpringSecurity提供的User类型。
+
+        return  new org.springframework.security.core.userdetails.User(
+                username, user.getPassword(),
+                grantedAuthorities  // 提供当前登录用户的权限集合,其中包括角色描述和权限描述。
+        );
+    }
+
+
+
+}

+ 63 - 0
common-springboot/src/main/resources/application.yml

@@ -0,0 +1,63 @@
+sso: 
+  root-path: login
+server:
+  port: 10021
+  servlet:
+    context-path: /sso
+  tomcat: 
+    uri-encoding: UTF-8
+    basedir: /data/tmp
+    
+spring: 
+  jackson:
+    time-zone: GMT+8
+    date-format: yyyy-MM-dd HH:mm:ss   
+    serialization: 
+      WRITE_DURATIONS_AS_TIMESTAMPS: false
+# Redis           
+  redis:
+    database: 1          # Redis数据库索引(默认为0)
+    timeout: 0            # 连接超时时间(毫秒)
+    host: 172.18.0.23
+    port: 6379
+    password: Ebe1tech/Passw0rd
+    jedis: 
+      pool:
+        max-idle: 10
+        max-active: 10
+        max-wait: 1000
+        
+# dataBase
+           
+  datasource: 
+   platform: mysql
+   url: jdbc:mysql://172.18.0.23:3306/common_security?characterEncoding=UTF-8&useSSL=false
+   username: root
+   password: 1q2w3e4r
+   driver-class-name: com.mysql.cj.jdbc.Driver
+   hikari: 
+     idle-timeout: 600000
+     validation-timeout: 1000
+     maximum-pool-size: 30
+     connection-timeout: 60000
+     transactionIsolation: TRANSACTION_READ_COMMITTED
+mybatis-plus:
+  mapper-locations:
+      - classpath:/mapper/**Mapper.xml
+  #实体扫描,多个package用逗号或者分号分隔
+  typeAliasesPackage: 
+  global-config:
+    db-config:
+      dbType: MYSQL
+      #主键类型  AUTO:"数据库ID自增", INPUT:"用户输入ID", ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
+      id-type: AUTO
+      #字段策略IGNORED:"忽略判断",NOT_NULL:"非 NULL 判断"),NOT_EMPTY:"非空判断"
+      field-strategy: NOT_NULL
+      #逻辑删除配置(下面3个配置)
+      logic-delete-value: 1
+      logic-not-delete-value: 0
+  configuration:
+    #驼峰下划线转换
+    map-underscore-to-camel-case: true
+    cache-enabled: false
+    jdbc-type-for-null: 'null'   

+ 3 - 0
common-springboot/src/main/resources/static/css/index.css

@@ -0,0 +1,3 @@
+.a{
+    text-align: center;
+}

+ 1 - 0
common-springboot/src/main/resources/static/js/index.js

@@ -0,0 +1 @@
+var a;

+ 10 - 0
common-springboot/src/main/resources/templates/fail.html

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
+<head>
+    <meta charset="UTF-8">
+    <title>认证失败</title>
+</head>
+<body style="text-align: center">
+    <h3><!--<span th:text="${SPRING_SECURITY_LAST_EXCEPTION.message}"></span>-->登录认证失败,请重新<a href="/sso/">登录</a></h3>
+</body>
+</html>

+ 26 - 0
common-springboot/src/main/resources/templates/login.html

@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
+<head>
+    <meta charset="UTF-8">
+    <title>登录页面</title>
+</head>
+<body style="text-align: center">
+    <h3>登录页面</h3>
+    <form action="/sso/login" method="post" style="margin: auto; width: 600px;">
+        <input type="text" name="_csrf" th:value="${_csrf.token}" th:if="${_csrf}">
+        <div>
+            <label>用户名:</label>
+            <input name="name" value="" type="text">
+        </div>
+        <div>
+            <label>密码:</label>
+            <input name="pswd" value="" type="text">
+        </div>
+        <div>
+
+            <input type="checkbox" name="rememberMe" value="true"> 记住我<br>
+            <input type="submit" value="登录">
+        </div>
+    </form>
+</body>
+</html>

+ 29 - 0
common-springboot/src/main/resources/templates/success.html

@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en"
+      xmlns:sec="http://www.w3.org/1999/xhtml"
+      xmlns:th="http://www.w3.org/1999/xhtml">
+<head>
+    <meta charset="UTF-8">
+    <title>认证成功</title>
+</head>
+<body style="text-align: center">
+    <h3>认证成功,<a href="/logout">退出</a></h3>
+    <div>
+        <!-- sec:authentication - 对应已认证用户的主体对象,Authentication类型对象。 -->
+        1<h3 sec:authentication="name"></h3><br>
+        2<h3 sec:authentication="principal"></h3><br>
+        3<h3 sec:authentication="credentials"></h3><br>
+        4<h3 sec:authentication="authorities"></h3>
+        <hr>
+
+        <!-- sec:authorize - 权限校验,相当于注解中的PreAuthorize,
+             标签属性的赋值方式,和注解的属性value赋值方式相同,和access方法的参数赋值方式相同。
+             hasRole('ROLE_xxx')   hasAuthority('xxx')
+          -->
+        <h3 sec:authorize="hasAuthority('userManagement:create')" th:text="3权限"></h3><br>
+        <h3 sec:authorize="hasAuthority('userManagement:modify')" th:text="4权限"></h3><br>
+        <h3 sec:authorize="hasAuthority('userManagement:drop')" th:text="5权限"></h3><br>
+        <h3 sec:authorize="hasRole('ROLE_管理员')" th:text="管理员角色"></h3><br>
+    </div>
+</body>
+</html>