SSM和SpringBoot整合SpringSecurity完成方法级权限控制(非常详细)

初识权限管理

权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源。权限管理几乎出现在任何系统里面,前提是需要有用户和密码认证的系统。

在权限管理的概念中,有两个非常重要的名词:
认证:通过用户名和密码成功登陆系统后,让系统得到当前用户的角色身份。
授权:系统根据当前用户的角色,给其授予对应可以操作的权限资源。

完成权限管理需要三个对象:

  • 用户:主要包含用户名,密码和当前用户的角色信息,可实现认证操作。
  • 角色:主要包含角色名称,角色描述和当前角色拥有的权限信息,可实现授权操作。
  • 权限:权限也可以称为菜单,主要包含当前权限名称,url地址等信息,可实现动态展示菜单。

这也是经典的 RBAC 模式:

RBAC 是基于角色的访问控制Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。

初识Spring Security

Spring Securityspring 采用AOP思想,基于 servlet 过滤器实现的安全框架。它提供了完善的认证机制方法级的授权功能。是一款非常优秀的权限管理框架。

入门案例

添加 SpringSecurity 坐标
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>
核心文件配置 - mvc 资源启用
<context:component-scan base-package="org.neuedu.security.demo.controller" />

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/pages/"/>
    <property name="suffix" value=".jsp" />
</bean>

<mvc:annotation-driven />
<mvc:default-servlet-handler />
核心文件配置 - 认证和资源拦截
<!--用户认证: 过滤器链转发到当前配置,进行 资源拦截,页面请求处理....-->
<!--设置可以用spring的el表达式配置Spring Security并自动生成对应配置组件(过滤器)-->
<!-- auto-config="true" use-expressions="true" -->
<security:http >
    <!--使用spring的el表达式来指定项目所有资源访问都必须进行认证-->
    <security:intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/>
    <security:form-login />
    <!-- <security:http-basic />  -->
</security:http>

<!--用户授权: 设置Spring Security认证用户信息的来源-->
<security:authentication-manager>
    <security:authentication-provider>
        <!--构建用户对象-->
        <security:user-service>
            <!--{noop} -- noop是no operate的意思,也就是说明保存的密码没有做加密操作-->
           <security:user name="user" password="{noop}user" authorities="ROLE_USER" />
           <security:user name="admin" password="{noop}admin" authorities="ROLE_ADMIN" />
        </security:user-service>
    </security:authentication-provider>
</security:authentication-manager>

auto-config="true" 自动配置,加上它,则可以省略 认证方式,即可以不用 `use-expressions="true"` 启用 SPEL 表达式,页面可以获取响应的认证对象 页面表单的形式认证
`` 页面弹出框的形式认证

配置 SpringSecurity 过滤器
<!--Spring Security过滤器链,注意过滤器名称必须叫springSecurityFilterChain-->
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<!-- SpringMVC 中央处理器 -->
<servlet>
    <servlet-name>springMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:application-*.xml</param-value>
    </init-param>
    <!--服务器启动就加载Servlet-->
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>springMVC</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
默认首页 index.jsp
<h1>SpringSecurity 权限管理 <a href="/logout">退出</a></h1>
<p><a href="dept/add">添加部门</a></p>
<p><a href="dept/del">删除部门</a></p>
<p><a href="dept/edit">修改部门</a></p>
<p><a href="dept/list">部门列表</a></p>
运行项目首页查看

在这里插入图片描述

运行可以看到,系统并没有进入我们期待的 index.jsp 页面,而是进入了一个登录页面,这个页面是由 SpringSecurity 框架为我们提供的,我们自己配置了拦截所有资源,也就是说,所有资源请求都需要认证通过才能继续访问。而对应的用户名和密码就是我们自己配置的 adminuser.

在这个登录页面上输入用户名 user,密码 user ,点击 Sign in,这样就可以进入 index.jsp 页面了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bROGRUz2-1668589974349)(.\images\1575021648945.png)]

到此,我们就完成了一个入门案例,但是我们在实际开发过程中,肯定不可能使用 SpringSecurity 提供的这个默认登录页面,不然不仅项目色调不一致,语言类型也不一致。

SpringSecurity使用自定义认证页面

查看 SringSecurity 默认提供的登录界面,获取对应的默认数据: 请求方式字段名称 , 请求地址

img

SpringSecurity 主配置文件中指定认证页面配置信息,注意:登录页面需要放行,否则会出现死循环

<!--用户认证-->
<!--直接释放无需经过SpringSecurity过滤器的静态资源-->
<security:http pattern="/fail.jsp" security="none" />

<security:http auto-config="true" use-expressions="true">
    <!--指定login.jsp页面可以被匿名访问,注意 pattern 必须以 / 开头 -->
    <security:intercept-url pattern="/login.jsp" access="permitAll()" />
    <security:intercept-url pattern="/dept/add" access="hasRole('ROLE_USER')" />
    <security:intercept-url pattern="/dept/del" access="hasRole('ROLE_ADMIN')" />
    <security:intercept-url pattern="/dept/edit" access="hasRole('ROLE_ADMIN')" />
    <security:intercept-url pattern="/dept/list" access="hasAnyRole('ROLE_ADMIN','ROLE_USER')" />
   <!--排除的认证信息都需要放在该认证之前,否则不生效-->
   <security:intercept-url pattern="/**" access="isFullyAuthenticated()" />

    <!--指定认证的登录页面-->
    <security:form-login login-page="/login.jsp"
                         username-parameter="username" 
                         password-parameter="password"
                         login-processing-url="/login"
                         default-target-url="/index.jsp"
                         authentication-failure-url="/fail.jsp" />
    <!--指定退出登录后跳转页面-->
    <security:logout logout-url="/logout" logout-success-url="/login.jsp" />
    
    <!--禁用crsf防护-->
    <security:csrf disabled="true" />

     <!--权限不足跳转的403页面-->
    <security:access-denied-handler error-page="/fail.jsp" />
    
</security:http>

<!--用户授权-->
<!--设置Spring Security认证用户信息的来源-->
<security:authentication-manager>
    <security:authentication-provider>
        <security:user-service>
           <security:user name="user" password="{noop}user" authorities="ROLE_USER" />
           <security:user name="admin" password="{noop}admin" authorities="ROLE_ADMIN" />
        </security:user-service>
    </security:authentication-provider>
</security:authentication-manager>
SpringSecurity 中的 SpringEL表达式
表达式	                       	  =			描述
hasRole([role])	          		=当前用户是否拥有指定角色。
hasAnyRole([role1,role2])  		=多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任									意一个则返回true。
hasAuthority([auth])	   		= 等同于hasRole
hasAnyAuthority([auth1,auth2])	=等同于hasAnyRole
Principle	                  	=代表当前用户的principle对象
authentication	              	=直接从SecurityContext获取的当前Authentication对象
permitAll	   				 	=总是返回true,表示允许所有的
denyAll							=总是返回false,表示拒绝所有的
isAnonymous()					=当前用户是否是一个匿名用户
isRememberMe()					=表示当前用户是否是通过Remember-Me自动登录的
isAuthenticated()				=表示当前用户是否已经登录认证成功了。
isFullyAuthenticated()			=如果当前用户既不是一个匿名用户,同时又不是通过Remember-Me自动登录									的,则返回true。

在这里插入图片描述

403什么异常?这是 SpringSecurity 中的权限不足!这个异常怎么来的?还记得上面登录页面源码中的那个_csrf隐藏input吗?问题就在这了!

SpringSecurity的csrf防护机制

CSRF(Cross-site request forgery)跨站请求伪造,是一种难以防范的网络攻击方式。

SpringSecurity 的 csrf 机制把请求方式分成两类来处理 - 【 CsrfFilter 】。
第一类 :“GET”, “HEAD”, “TRACE”, "OPTIONS"四类请求可以直接通过.
第二类 :除去上面四类,包括 POST 都要被验证携带 token 或者 关闭 csrf 防护才能通过.

  • SpringSecurity 主配置文件中添加禁用crsf防护的配置 : ``
  • 在认证页面携带 token 请求: 导入 SpringSecurity 标签库,在表单中录入:``

注意:一旦开启了csrf防护功能,logout处理器便只支持POST请求方式了!

SpringSecurity 原理分析

SpringSecurity 流程图

在这里插入图片描述

  1. 客户端发起一个请求,进入 Security 过滤器链
  2. 当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler ,如果登出成功则到 logoutSuccessHandler 登出成功处理,如果登出失败则由 ExceptionTranslationFilter ;如果不是登出路径则直接进入下一个过滤器。
  3. 当到 UsernamePasswordAuthenticationFilter 的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作,如果登录失败则到 AuthenticationFailureHandler 登录失败处理器处理,如果登录成功则到 AuthenticationSuccessHandler 登录成功处理器处理,如果不是登录请求则不进入该过滤器。
  4. 当到 FilterSecurityInterceptor 的时候会拿到 uri ,根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层否则到 AccessDeniedHandler 鉴权失败处理器处理。

Spring Security常用过滤器介绍

  • SecurityContextPersistenceFilter : 在每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中,然后在该次请求处理完成之后,将 SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将 SecurityContextHolder 中的信息清除,例如在Session中维护一个用户的安全信息就是这个过滤器处理的。

  • CsrfFilter : 用于处理跨站请求伪造

  • UsernamePasswordAuthenticationFilter : 用于处理基于表单的登录请求,从表单中获取用户名和密码。认证操作全靠这个过滤器,默认匹配URL为 /login 且必须为 POST 请求, 默认使用的表单 name 值为 usernamepassword ,这两个值可以通过设置这个过滤器的 usernameParameterpasswordParameter 两个参数的值进行修改。

  • DefaultLoginPageGeneratingFilter : 如果没有在配置文件中指定认证页面,则由该过滤器生成一个默认认证页面。

  • LogoutFilter : 匹配URL为 /logout 的请求,实现用户退出,清除认证信息。

  • DefaultLogoutPageGeneratingFilter : 由此过滤器可以生产一个默认的退出登录页面

  • AnonymousAuthenticationFilter : 当SecurityContextHolder中认证信息为空,则会创建一个匿名用户存入到 SecurityContextHolder 中。

  • FilterSecurityInterceptor : 以看做过滤器链的出口。获取所配置资源访问的授权信息,根据SecurityContextHolder中存储的用户信息来决定其是否有权限。

  • RememberMeAuthenticationFilter : 当用户没有登录而直接访问资源时, 从 cookie 里找出用户的信息, 如果 Spring Security 能够识别出用户提供的 remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启

核心类简介

  • Authentication 是一个接口,用来表示用户认证信息的,在用户登录认证之前相关信息会封装为一个 Authentication 具体实现类的对象,在登录认证成功之后又会生成一个信息更全面,包含用户权限等信息的 Authentication 对象,然后把它保存在 SecurityContextHolder 所持有的 SecurityContext 中,供后续的程序进行调用,如访问权限的鉴定等。

  • SecurityContextHolder 是用来保存 SecurityContext 的。SecurityContext 中含有当前正在访问系统的用户的详细信息。

  • AuthenticationManager 是一个用来处理认证(Authentication)请求的接口。在其中只定义了一个方法 authenticate(),该方法只接收一个代表认证请求的 Authentication 对象作为参数,如果认证成功,则会返回一个封装了当前用户权限等信息的 Authentication 对象进行返回。

    校验认证请求最常用的方法是根据请求的用户名加载对应的 UserDetails,然后比对 UserDetails 的密码与认证请求的密码是否一致,一致则表示认证通过。在认证成功以后会使用加载的 UserDetails 来封装要返回的 Authentication 对象,加载的 UserDetails 对象是包含用户权限等信息的。认证成功返回的 Authentication 对象将会保存在当前的 SecurityContext 中。

  • UserDetailsService 通过 Authentication.getPrincipal() 返回的其实是一个 UserDetails 实例UserDetailsSpring Security 中一个核心的接口。其中定义了一些可以获取用户名、密码、权限等与认证相关的信息的方法。Spring Security 内部使用的 UserDetails 实现类大都是内置的 User 类,我们如果要使用 UserDetails 时也可以直接使用该类。

UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext()
                                                .getAuthentication().getPrincipal();
String username = userDetails.getUsername();
123

初识自定义认证

UsernamePasswordAuthenticationFilter 拦截认证, 请求必须是 POST , 填写的用户名(username)和密码(password)会封装到 UsernamePasswordAuthenticationToken 中 , 调用 AuthenticationManager 对象实现认证. 实现类 AuthenticationProvider 完成认证业务,我们可以直接编写一个 UserDetailsService 的实现类返回一个UserDetails 对象即可。这里需要注意返回的对象中需要带有权限信息

//自定义认证业务逻辑
public class UserService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //User(String username, String password, Collection<? extends GrantedAuthority> authorities)
        //User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities)
        List<GrantedAuthority> roles = new ArrayList<>();
        roles.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        roles.add(new SimpleGrantedAuthority("ROLE_USER"));
        //User user = new User("admin","{noop}admin",roles);
        User user = new User("admin","{noop}admin",true,true,true,true,roles);
        return user;
    }
}

设置用户状态

在用户认证业务里,认证过程中,这四个参数必须同时为 true 认证才能通过,当然这四个字段我们也可以把它添加到数据库字段中,这样也可以完成动态设置.

  • boolean enabled 是否可用
  • boolean accountNonExpired 账户是否失效
  • boolean credentialsNonExpired 认证是否过期
  • boolean accountNonLocked 账户是否锁定

使用数据库完成动态认证

上面的案例我们是自己模拟出一个用户信息和角色权限,接下来我们使用数据库中动态的数据来完成认证,其实很简单我们只需要在数据库中添加对应的用户表和角色表,然后添加两个方法 根据用户名查询用户信息 , 根据用户ID查询角色集合 , 然后动态的去替换上面 UserService 中的模拟数据即可。接下来我们开始搭建后端环境。

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.6</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus</artifactId>
    <version>3.1.0</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.16</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.6</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.14</version>
</dependency>

<!--mp 代码生成器-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.1.0</version>
</dependency>

<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.28</version>
</dependency>

后端数据库整合

RBAC 数据表结构

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission`  (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
  `permission_NAME` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '菜单名称',
  `permission_url` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '菜单地址',
  `parent_id` int(11) NOT NULL DEFAULT 0 COMMENT '父菜单id',
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES (1, '部门添加 ', '/dept/add', 0);
INSERT INTO `sys_permission` VALUES (2, '部门删除', '/dept/del', 0);
INSERT INTO `sys_permission` VALUES (3, '部门修改', '/dept/edit', 0);
INSERT INTO `sys_permission` VALUES (4, '部门列表', '/dept/list', 0);

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
  `ROLE_NAME` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '角色名称',
  `ROLE_DESC` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '角色描述',
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'ROLE_ADMIN', '管理员角色');
INSERT INTO `sys_role` VALUES (2, 'ROLE_USER', '普通用户角色');

-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission`  (
  `RID` int(11) NOT NULL COMMENT '角色编号',
  `PID` int(11) NOT NULL COMMENT '权限编号',
  PRIMARY KEY (`RID`, `PID`) USING BTREE,
  INDEX `FK_Reference_12`(`PID`) USING BTREE,
  CONSTRAINT `FK_Reference_11` FOREIGN KEY (`RID`) REFERENCES `sys_role` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `FK_Reference_12` FOREIGN KEY (`PID`) REFERENCES `sys_permission` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES (1, 1);
INSERT INTO `sys_role_permission` VALUES (1, 2);
INSERT INTO `sys_role_permission` VALUES (2, 3);
INSERT INTO `sys_role_permission` VALUES (2, 4);

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名称',
  `password` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
  `status` int(1) DEFAULT 1 COMMENT '1开启0关闭',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'user', '{noop}user', 1);
INSERT INTO `sys_user` VALUES (2, 'admin', '{noop}admin', 1);

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `UID` int(11) NOT NULL COMMENT '用户编号',
  `RID` int(11) NOT NULL COMMENT '角色编号',
  PRIMARY KEY (`UID`, `RID`) USING BTREE,
  INDEX `FK_Reference_10`(`RID`) USING BTREE,
  CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `sys_role` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `sys_user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (2, 1);
INSERT INTO `sys_user_role` VALUES (1, 2);
INSERT INTO `sys_user_role` VALUES (2, 2);

MP 代码生成器

//读取属性配置文件
private ResourceBundle rb = ResourceBundle.getBundle("druid");

@Test
public void codeGenerator(){
    // 代码生成器
    AutoGenerator mpg = new AutoGenerator();

    // 全局配置
    GlobalConfig gc = new GlobalConfig();
    String projectPath = System.getProperty("user.dir");
    gc.setOutputDir(projectPath + "/src/main/java");
    gc.setAuthor("CDHong");
    gc.setOpen(false);
    gc.setSwagger2(false); //实体属性 Swagger2 注解
    mpg.setGlobalConfig(gc);

    // 数据源配置
    DataSourceConfig dsc = new DataSourceConfig();
    dsc.setUrl(rb.getString("url"));
    // dsc.setSchemaName("public");
    dsc.setDriverName(rb.getString("driver"));
    dsc.setUsername(rb.getString("user"));
    dsc.setPassword(rb.getString("pwd"));
    mpg.setDataSource(dsc);

    // 包配置
    PackageConfig pc = new PackageConfig();
    //父级公用包名,就是自动生成的文件放在项目路径下的那个包中
    pc.setParent("org.neuedu.spring.security.demo");
    mpg.setPackageInfo(pc);

    // 自定义配置
    InjectionConfig cfg = new InjectionConfig() {
        @Override
        public void initMap() {
            // to do nothing
        }
    };
    String templatePath = "/templates/mapper.xml.ftl";
    // 自定义输出配置
    List<FileOutConfig> focList = new ArrayList<>();
    // 自定义配置会被优先输出
    focList.add(new FileOutConfig(templatePath) {
        @Override
        public String outputFile(TableInfo tableInfo) {
            // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
            return projectPath + "/src/main/resources/mappers/" +
                tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
        }
    });

    cfg.setFileOutConfigList(focList);
    mpg.setCfg(cfg);

    // 配置模板
    TemplateConfig templateConfig = new TemplateConfig();
    templateConfig.setXml(null); //是否在mapper接口处生成xml文件
    mpg.setTemplate(templateConfig); //设置模板引擎
    mpg.setTemplateEngine(new FreemarkerTemplateEngine());

    // 策略配置
    StrategyConfig strategy = new StrategyConfig();
    strategy.setNaming(NamingStrategy.underline_to_camel); //Entity文件名称命名规范
    strategy.setColumnNaming(NamingStrategy.underline_to_camel);//Entity字段名称命名规范
    strategy.setEntityLombokModel(true);  //是否使用lombok完成Entity实体标注
    strategy.setRestControllerStyle(true);  //Controller注解使用是否RestController标注
    strategy.setControllerMappingHyphenStyle(true); //Controller注解名称,使用连字符(—)
    strategy.setInclude("sys_user","sys_role","sys_permission"); //要生成的表名,不写默认所有
    //strategy.setTablePrefix("sys_");//表前缀,添加该表示,则生成的实体,不会有表前缀
    //strategy.setFieldPrefix("sys_");  //字段前缀
    mpg.setStrategy(strategy);
    mpg.execute();
}

SpringSecurity + MP 运行环境

<context:property-placeholder location="classpath:druid.properties" />

<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"  destroy-method="close">
    <property name="driverClassName" value="${driver}" />
    <property name="url" value="${url}" />
    <property name="username" value="${user}" />
    <property name="password" value="${pwd}" />

    <property name="initialSize" value="${initSize}" />
    <property name="maxWait" value="${maxWait}" />
    <property name="maxActive" value="${maxSize}" />
    <property name="minIdle" value="${minSize}" />
</bean>

<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
    <property name="dataSource" ref="druidDataSource" />
    <property name="typeAliasesPackage" value="org.neuedu.spring.security.demo.entity" />
    <property name="mapperLocations" value="classpath:mappers/*Mapper.xml" />
    <property name="plugins">
        <bean class="com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor" />
    </property>
</bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="org.neuedu.spring.security.demo.mapper" />
</bean>

<context:component-scan base-package="org.neuedu.spring.security.demo.service.impl" />


测试数据录入

controller 中添加一个 list 请求方法,测试整合端是否 OK。

@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseEntity {
    private int code;
    private String msg;
    private Long count;
    private Object data;

    public static ResponseEntity ok(){
        return new ResponseEntity(0,null,null,null);
    }
    public static ResponseEntity ok(String msg){
        return new ResponseEntity(0,msg,null,null);
    }
    public static ResponseEntity error(String msg){
        return new ResponseEntity(1,msg,null,null);
    }

    public static ResponseEntity data(Object obj){
        return new ResponseEntity(0,null,null,obj);
    }

    public static ResponseEntity page(long count,Object obj){
        return new ResponseEntity(0,null,count,obj);
    }

    public static boolean isSuccess(ResponseEntity responseEntity){
        return responseEntity.getCode() == 1;
    }

}

动态认证逻辑替换

在角色Mapper中添加一个查询角色的方法

public interface SysRoleMapper extends BaseMapper<SysRole> {

    @Select(" select r.id,r.role_name,r.role_desc from sys_user u " +
            " join sys_user_role ur on u.id = ur.UID " +
            " join sys_role r on r.id = ur.RID " +
            " where u.id = #{userId}  ")
    List<SysRole> findRoldByUserId(Integer userId);

}

动态权限认证更改

修改 SysUserServiceImp 类,实现UserDetailsService 接口,重写 loadUserByUsername 方法,完成认证逻辑,动态替换认证数据:

@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService, UserDetailsService {

    @Autowired
    private SysRoleMapper roleMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        //根据用户名获取对应的用户信息  User
        SysUser user = new LambdaQueryChainWrapper<SysUser>(baseMapper).eq(SysUser::getUsername,username).one();
        if(Objects.isNull(user)){
            return null;
        }
        //获取权限集合  SimpleGrantedAuthority
        List<SysRole> roles = roleMapper.findRoldByUserId(user.getId());

        //User
        Set<SimpleGrantedAuthority> authorities = new HashSet<>();
        roles.forEach(role-> authorities.add(new SimpleGrantedAuthority(role.getRoleName())));

        return new User(user.getUsername(),user.getPassword(),user.getStatus()==1,true,true,true,authorities);
    }
}

修改 application-security.xml 中的认证逻辑引用

<!-- 认证 -->
<security:authentication-manager>
    <security:authentication-provider user-service-ref="sysUserServiceImpl" />
</security:authentication-manager>

到此完毕,但是,在认证逻辑中我们都是手动组装数据,这样比较麻烦,有没有办法可以简化呢?

接下来,我们只需要把 SysUser 类变成 UserDetails的子类 , 把 SysRole 类变成 GrantedAuthority 的子类是不是就可以了呢?

使用Java多态性,简化代码

数据库实体关系

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("sys_role")
public class Role implements Serializable, GrantedAuthority {

    private static final long serialVersionUID=1L;

    // 编号
    @TableId(value = "ID", type = IdType.AUTO)
    private Integer id;

    // 角色名称
    @TableField("ROLE_NAME")
    private String roleName;

    //角色描述
    @TableField("ROLE_DESC")
    private String roleDesc;


    @Override
    public String getAuthority() {
        //返回用于认证的角色描述信息 ROLE_ADMIN,ROLE_USER 这类用于判断的对应字段
        return this.roleName;
    }
}

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("sys_user")
public class User implements Serializable, UserDetails {

private static final long serialVersionUID=1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    //用户名称
    private String username;

    //密码
    private String password;

    //1开启0关闭
    private Integer status;

    //拥有的所有角色
    @TableField(exist = false) //数据库中不存在该字段,使用注解排除
    private List<Role> roles = new ArrayList<>();

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //返回当前用户认证角色集合
        return roles;
    }

    //账户是否失效
    @Override
    public boolean isAccountNonExpired() {return true;}

    //账户是否锁定
    @Override
    public boolean isAccountNonLocked() {return true;}

    //认证是否过期
    @Override
    public boolean isCredentialsNonExpired() {return true;}

    //是否可用,使用数据库的用户状态来进行动态处理
    @Override
    public boolean isEnabled() {return this.getStatus()==1;}
}

数据库 RoleMapper 认证方法实现

public interface RoleMapper extends BaseMapper<Role> {

    @Select("SELECT r.ID,r.ROLE_NAME,r.ROLE_DESC FROM sys_user u " +
            " join sys_user_role ur on u.id = ur.UID " +
            " join sys_role r on r.ID = ur.RID " +
            " where u.id = #{userId} ")
    List<Role> findByUserId(Integer userId);

}

public interface UserMapper extends BaseMapper<User> {

    //注意:一定要提供一个 coloum = "查询字段" , 否则 MP 会直接去找属性叫 roles 的字段,直接报错
    @Results({
        @Result(id = true,property = "id",column = "id"),
        @Result(property = "roles",column = "id",many = @Many(select =              "org.neuedu.security.demo.mapper.RoleMapper.findByUserId"))
    })
    @Select("select u.id,u.username,u.password,u.status from sys_user u where u.username = #{username} ")
    User findByName(String username);

}

具体认证逻辑实现

@Slf4j
@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService, UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = baseMapper.findByName(username);
        log.info("登录用户信息:{}",user);
        log.info("登录用户拥有的认证角色:{}",user.getAuthorities());
        return user;
    }
}

指定认证使用的业务对象

<!--设置Spring Security认证用户信息的来源-->
<security:authentication-manager>
    <security:authentication-provider user-service-ref="userService">
    </security:authentication-provider>
</security:authentication-manager>

到此,大功告成,可以到页面是测试数据库动态权限,是否OK。

密码加密与记住我功能

加密认证功能实现

SpringSecurity 提供了很多种密码加密的形式,而我们之前为了简单,我们使用了明文不加密的形式登录,现在我们来看看它具体的加密形式怎么使用,先修改数据库中用户的密码,去掉 {noop} 改成指定加密方式的密码:

<!--加密对象-->
<bean id="passwordEncoder"
      class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

<!--设置Spring Security认证用户信息的来源-->
<security:authentication-manager>
    <security:authentication-provider user-service-ref="userServiceImpl">
        <!--指定认证使用的加密对象-->
        <security:password-encoder ref="passwordEncoder"/>
    </security:authentication-provider>
</security:authentication-manager>

这里去掉 {noop},密码需要加密后在入库,否则密码不匹配。

@Test
public void test(){
    BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
    String admin = passwordEncoder.encode("admin");
    String user = passwordEncoder.encode("user");
    System.out.println(admin);
    //$2a$10$GWHwW0.oU1FRlyAgR3ZMyuE1SlRlzPMfYktNG56n4oPnQCmm9Rpg.
    System.out.println(user);
    //$2a$10$SpT/iNJh3jYTbTFZEpXl8OFd60Hc18SmRU7OmwhWh93CdvvIW2G4C
}

记住我功能

remember me 功能,到 AbstractRememberMeServices 类中查看 loginSuccess 方法:登录的时候我们传递一个参数 remember-me ,如果它的值为 trueonyes1 其中一个,则表示页面勾选了记住我选项了。具体业务逻辑由 PersistentTokenBasedRememberMeServices 完成。在这里还得注意需要开启记住我功能的过滤器。注意:验证方式不能使用 isFullyAuthenticated() , 否则记住我这个功能无法成功

在这里插入图片描述

<!--设置可以用spring的el表达式配置Spring Security并自动生成对应配置组件(过滤器)-->
<security:http auto-config="true" use-expressions="true">
    <!--
        开启remember me过滤器,
        data-source-ref="dataSource" 指定数据库连接池 需要手动创建一个存储cookie的数据表
        token-validity-seconds="60" 设置token存储时间为60秒 可省略
    	remember-me-parameter="remember-me" 指定记住的参数名 可省略
    -->
<security:remember-me data-source-ref="dataSource" 
                      token-validity-seconds="60"
					  remember-me-parameter="remember-me"/>
</security:http>

CREATE TABLE `persistent_logins` (
    `username` varchar(64) NOT NULL,
    `series` varchar(64) NOT NULL,
    `token` varchar(64) NOT NULL,
    `last_used` timestamp NOT NULL,
    PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

注意这张表的名称和字段都是固定的,不要修改。在我们完成认证的时候,该数据库会有相应的记录来存储记住我的 cookie 值

<security:remember-me token-validity-seconds="600" 
                   token-repository-ref="jdbcTokenRepository" />

<bean id="jdbcTokenRepository" class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl">
 <property name="dataSource" ref="druidDataSource" />
 <!--项目启动前,创建 cookie 存储数据表,但是下次再启动项目的时候需要删除该字段-->
 <property name="createTableOnStartup" value="true" />
</bean>

用户资源动态授权

控制每一个前端请求的权限,配置如下:

<!--放行-->
<security:http pattern="/favicon.ico" security="none" />
<security:http pattern="/failure.jsp" security="none" />
<security:http pattern="/login.jsp" security="none" />
<!--授权  需要登录操作-->
<security:http auto-config="true" use-expressions="true">
    <security:intercept-url pattern="/dept/add" access="hasRole('ROLE_ADMIN')" />
    <security:intercept-url pattern="/dept/del" access="hasRole('ROLE_ADMIN')" />
    <security:intercept-url pattern="/dept/edit" access="hasRole('ROLE_USER')" />
    <security:intercept-url pattern="/dept/list" access="hasAnyRole('ROLE_USER','ROLE_ADMIN')" />
    <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER','ROLE_ADMIN')" />

前端页面控制

Spring Security 也有对 Jsp 标签支持的标签库。其中一共定义了三个标签:authorizeauthenticationaccesscontrollist(不常用)。其中 authentication 标签是用来代表当前 Authentication对象的,我们可以利用它来展示当前 Authentication 对象的相关信息。另外两个标签是用于权限控制的,可以利用它们来包裹需要保护的内容,通常是超链接和按钮。

  • authorize
    

    : 是用来判断普通权限的,通过判断用户是否具有对应的权限而控制其所包含内容的显示,其可以指定如下属性。

    • access : 需要使用表达式来判断权限,当表达式的返回结果为true时表示拥有对应的权限
      需要注意的是因为access属性是使用表达式的,需要设置http元素的use-expressions="true"存储。
    • ifAllGranted : 不能使用表达式,由逗号分隔的权限列表,用户必须拥有所有列出的权限时显示
    • ifAnyGranted : 不能使用表达式,用户必须至少拥有其中的一个权限时才能显示
    • ifNotGranted : 不能使用表达式,用户未拥有所有列出的权限时才能显示
  • authentication
    

    : 用来代表当前

    Authentication
    

    对象,主要用于获取当前

    Authentication
    

    的相关信息

    • property : 主要属性,我们可以通过它来获取当前 Authentication 对象的相关信息
    • scope : 定义var存在的范围,默认是 pageContext
    • var : 定义一个变量 , 将其以指定的属性名进行存放,默认是存放在 pageConext
    • htmlScape : 是否需要将 html 进行转义。默认为 true
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>
12345
<%@taglib uri="http://www.springframework.org/security/tags" prefix="security" %>
    
<security:authorize access="hasAnyRole('ROLE_ADMIN')">
    <p><a href="dept/add">部门添加</a></p>
</security:authorize>

<!-- 需要拥有所有的权限 -->
<sec:authorize ifAllGranted="ROLE_ADMIN">
    <a href="admin.jsp">admin</a>
</sec:authorize>

<!-- 只需拥有其中任意一个权限 -->
<sec:authorize ifAnyGranted="ROLE_USER,ROLE_ADMIN">hello</sec:authorize>
    
<!-- 不允许拥有指定的任意权限 -->
<sec:authorize ifNotGranted="ROLE_ADMIN">
    <a href="user.jsp">user</a>
</sec:authorize>

<!-- 获取 认证对象信息 -->
欢迎你:<security:authentication property="principal.username" />
或者
欢迎你:<security:authentication property="name" />

SpringSecurity 可以通过注解的方式来控制类或者方法的访问权限。注解需要对应的注解支持,若注解放在
controller 类中,对应注解支持应该放在 mvc 配置文件中,因为 controller类是有 mvc 配置文件扫描并创建的,同理,注解放在 service 类中,对应注解支持应该放在 spring 配置文件中。由于我们现在是模拟业务操作,并没有 service 业务代码,所以就把注解放在 controller类中了。

服务端方法级权限控制

在服务的我们可以通过 SpringSecurity 提供的注解对方法来进行权限控制,SpringSecurity 在方法的权限控制上支持三种注解: JSR-250注解 , @Secured注解 , 支持表达式的注解 。这三种注解默认是没有开启的,需要单独通过 global-method-security 元素对应的属性进行启用

<!--开启服务端方法级权限控制-->
<security:global-method-security jsr250-annotations="enabled" />
<security:global-method-security secured-annotations="enabled" />
<security:global-method-security pre-post-annotations="enabled" />

也可通过注解开启 , 需要在继承 WebSecurityConfigurerAdapter 类上加@EnableGlobalMethodSecurity 注解,并在该类中将 AthenticationManager 定义为 Bean .

JSR-250 注解使用
  • @RolesAllowed({"USER","ADMIN"}) 具有两种权限中的一种,就可以访问。这里可以省略前缀 ROLE_
  • @PermitAll 表示允许所有的角色进行访问,也就是说不进行权限控制
  • @DenyAll 表示无论什么角色都不可以访问,与 @PermitAll 相反
@Secured注解

JSR-250注解 使用一致,只是这个注解是 SpringSecurity 默认提供的,使用的时候不用额外引入 坐标 ,还有一点就是这个注解的角色需要加上前缀 ROLE_

支持 SPEL 表达式的注解
  • @PreAuthorize("hasRole('ADMIN')") 在方法调用之前,基于表达式的计算结果来限制对方法的访问
  • @PostAuthorize 允许方法调用,但是如果表达式计算结果为 false ,将抛出一个安全性异常
  • @PostFilter 允许方法调用,但必须按照表达式来过滤方法的结果
  • @PreFilter 允许方法调用,但必须在进入方法之前过滤输入值

案例演示

<!--开启权限控制注解支持-->
<security:global-method-security pre-post-annotations="enabled" />

在注解支持对应类或者方法上添加注解
@RestController
@RequestMapping("/dept")
public class DeptController {

    @Autowired
    private ISysUserService userService;

    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @GetMapping("/add")
    public String add(){
        return "dept add  .....  ";
    }

    @PreAuthorize("hasRole('ROLE_ADMIN') and #id==5 ")
    @GetMapping("/del")
    public String del(Integer id){
        return id+"dept del  .....  ";
    }

    @PreAuthorize("hasRole('ROLE_USER')")
    @GetMapping("/edit")
    public String edit(){
        return "dept edit  .....  ";
    }

    @PostAuthorize("returnObject.username.equals('admin')")
    @GetMapping("/list")
    public SysUser list(Integer id){
         return userService.getById(id);
    }

}

权限不足异常处理 - 编写异常处理器
@ControllerAdvice
public class ControllerExceptionAdvice {
    //只有出现AccessDeniedException异常才调转403.jsp页面
    @ExceptionHandler(AccessDeniedException.class)
    public String exceptionAdvice(){
        return "forward:/403.jsp";
    }
}

整合 SpringBoot

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.3.RELEASE</version>
    <relativePath/>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
</dependencies>

@RestController
@RequestMapping("/product")
public class ProductController {
    
    @RequestMapping
    public String hello(){
        return "success";
    }
}

SpringBoot 已经为 SpringSecurity 提供了默认配置,默认所有资源都必须认证通过才能访问,那么问题来了!此刻并没有连接数据库,也并未在内存中指定认证用户,如何认证呢?其实SpringBoot已经提供了默认用户名 user ,密码在项目启动时随机生成,在日志中可以查看到:

在这里插入图片描述

整合 Jsp 模板

SpringBoot 官方是不推荐在 SpringBoot 中使用 jsp 的,那么到底可以使用吗?答案是肯定的!
不过需要导入 tomcat 插件启动项目,不能再用 SpringBoot 默认 tomcat 了。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
</dependency>

核心配置文件中配置视图解析器

spring.mvc.view.prefix=/pages/
spring.mvc.view.suffix=.jsp

提供 SpringSecurity 配置类

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //先不连接数据库,提供静态用户名和密码
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("user")
            .password("{noop}123")
            .roles("USER");
    }
    
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/login.jsp", "/failer.jsp", "/css/**", "/img/**",
                         "/plugins/**").permitAll()
            .antMatchers("/**").hasAnyRole("USER")
            .anyRequest()
            .authenticated()
            .and()
            .formLogin()
            .loginPage("/login.jsp")
            .loginProcessingUrl("/login")
            .successForwardUrl("/index.jsp")
            .failureForwardUrl("/failer.jsp")
            .permitAll()
            .and()
            .logout()
            .logoutUrl("/logout")
            .invalidateHttpSession(true)
            .logoutSuccessUrl("/login.jsp")
            .permitAll()
            .and()
            .csrf()
            .disable();
    }
}

数据库认证

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.2.0</version>
</dependency>

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql:///security_authority
spring.datasource.username=root
spring.datasource.password=root

logging.level.org.neuedu=debug

创建角色 pojo 对象 - 这里直接使用 SpringSecurity 的角色规范

@Data
public class SysRole implements GrantedAuthority {
    private Integer id;
    private String roleName;
    private String roleDesc;
    
    //标记此属性不做json处理
    @JsonIgnore
    @Override
    public String getAuthority() {
        return roleName;
    }
}

创建用户 pojo 对象,这里直接实现 SpringSecurity 的用户对象接口,并添加角色集合私有属性。

@Data
public class SysUser implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    private Integer status;
    private List<SysRole> roles = new ArrayList<>();
    
    @JsonIgnore
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles;
    }
    
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    
    @JsonIgnore
    @Override
    public boolean isEnabled() {
        return true;
    }
}

提供角色 mapper 接口

public interface RoleMapper extends Mapper<SysRole> {
    @Select("SELECT r.id, r.role_name roleName, r.role_desc roleDesc " +
            "FROM sys_role r, sys_user_role ur " +
            "WHERE r.id=ur.rid AND ur.uid=#{uid}")
    public List<SysRole> findByUid(Integer uid);
}

提供用户mapper接口

public interface UserMapper extends Mapper<SysUser> {
    @Select("select * from sys_user where username=#{username}")
    @Results({
        @Result(id = true, property = "id", column = "id"),
        @Result(property = "roles", column = "id", javaType = List.class,
                many = @Many(select = "com.itheima.mapper.RoleMapper.findByUid"))
    })
    public SysUser findByUsername(String username);
}

提供认证 service 接口

import org.springframework.security.core.userdetails.UserDetailsService;

public interface UserService extends UserDetailsService {
}

提供认证service实现类

@Service
@Transactional
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        return userMapper.findByUsername(s);
    }
}

启动类中加入 Mapper 接口扫描 以及 密码加密 Bean 对象

@SpringBootApplication
@MapperScan("org.neuedu.security.mapper")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(QuickStartApplication.class, args);
    }
    
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

}

修改配置类

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder);
    }
    
    protected void configure(HttpSecurity http) throws Exception {
        http
            允许不登陆就可以访问的方法,多个用逗号分隔
            .authorizeRequests()
            .antMatchers("/login.jsp", "/failer.jsp", "/css/**", "/img/**",
                         "/plugins/**").permitAll()
            .antMatchers("/**").hasAnyRole("USER")
            //其他的需要授权后访问
            .anyRequest()
            .authenticated()
            .and()
            //表单登录
            .formLogin()
            .loginPage("/login.jsp")
            .loginProcessingUrl("/login")
            .successForwardUrl("/index.jsp")
            .failureForwardUrl("/failer.jsp")
            .permitAll()
            .and()
            //退出
            .logout()
            .logoutUrl("/logout")
            .invalidateHttpSession(true)
            .logoutSuccessUrl("/login.jsp")
            .permitAll()
            .and()
            //关闭 csrf
            .csrf()
            .disable();
    }
}

整合实现授权功能

在启动类上添加开启方法级的授权注解 @EnableGlobalMethodSecurity(prePostEnabled= true)

在产品处理器类上添加注解 @PreAuthorize('ADMIN')

@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/test")
public String test(){
    return "info";
}

指定自定义异常页面

编写异常处理器拦截403异常

@ControllerAdvice
public class HandleControllerException {
    
    @ExceptionHandler(RuntimeException.class)
    public String exceptionHandler(RuntimeException e){
        if(e instanceof AccessDeniedException){
            //如果是权限不足异常,则跳转到权限不足页面!
            return "redirect:/403.jsp";
        }
        //其余的异常都到500页面!
        return "redirect:/500.jsp";
    }
    
}


整合Thymeleaf

认证

使用默认用户名 user 与 控制台生成的随机密码进行登录

#修改配置文件,自定义用户名和密码
spring.security.user.name=admin
spring.security.user.password=admin
123
//继承 WebSecurityConfigurerAdapter 重写 configure(auth) 方法,代码指定用户名和密码
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //认证相关  用户名密码
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //基于内存
        auth.inMemoryAuthentication()
            .withUser("admin").password("{noop}admin").roles("ROLE_ADMIN")
            .and()
            .withUser("user").password("{noop}user").roles("ROLE_USER");
        //基于数据库
    }
}

授权

//继承 WebSecurityConfigurerAdapter 重写 configure(http) 方法,完成授权操作
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/hello").hasAnyRole("ADMIN")
        .anyRequest().authenticated();

    //表单登录
    http.formLogin()
 
}

自定义登录页面

//授权,请求相关
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/logPage").permitAll()
        .antMatchers("/hello").hasAnyRole("ADMIN")
        .anyRequest().authenticated();

    //表单的配置
    http.formLogin()
        .usernameParameter("logName").passwordParameter("logPwd")
        .loginPage("/logPage").loginProcessingUrl("/login");

    //csrf
    http.csrf().disable();
}

记住我功能

//授权,请求相关
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/logPage").permitAll()
        .antMatchers("/hello").hasAnyRole("ADMIN")
        .anyRequest().authenticated();

    //表单的配置
    http.formLogin()
        .usernameParameter("logName").passwordParameter("logPwd")
        .loginPage("/logPage").loginProcessingUrl("/login");

    //记住我
    http.rememberMe().rememberMeParameter("forgetMe").rememberMeCookieName("forgetMe")
        .tokenValiditySeconds(5);
    
    //csrf
    http.csrf().disable();
}

基于数据库认证

  • 角色名称需要注意:加上 ROLE_ 前缀,做判断的时候,可以不用省略
  • 用户登录密码:如果是明文登录需要加上{noop} 前缀,否则需要生成加密密码在存储到数据表中
-- ----------------------------
-- Table structure for sys_authority
-- ----------------------------
DROP TABLE IF EXISTS `sys_authority`;
CREATE TABLE `sys_authority`  (
  `id` int(11) NOT NULL COMMENT '主键编号',
  `authority_name` varchar(25)  COMMENT '权限名称',
  `authority_url` varchar(25)  COMMENT '权限地址',
  `icon` varchar(25)  COMMENT '图标',
  `parent_id` int(11) DEFAULT NULL COMMENT '上级模块',
  `permission` varchar(255)  COMMENT '权限值',
  `sort_num` int(3) DEFAULT NULL COMMENT '排序号',
  `remark` varchar(200)  COMMENT '备注',
  `create_time` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
  PRIMARY KEY (`id`) USING BTREE
) COMMENT = '权限';

-- ----------------------------
-- Records of sys_authority
-- ----------------------------
INSERT INTO `sys_authority` VALUES (1, '用户管理', '/user/list', NULL, NULL, 'user:add,user:del', NULL, NULL, '2020-01-03 14:14:06');

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键编号',
  `role_name` varchar(25)  COMMENT '角色名称',
  `remark` varchar(200)  COMMENT '备注',
  `create_time` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
  PRIMARY KEY (`id`) USING BTREE
) COMMENT = '角色';

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'ROLE_ADMIN', '系统管理员', '2020-01-03 14:12:47');
INSERT INTO `sys_role` VALUES (2, 'ROLE_MGR', '销售主管', '2020-01-03 14:12:51');

-- ----------------------------
-- Table structure for sys_role_authority
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_authority`;
CREATE TABLE `sys_role_authority`  (
  `id` int(11) NOT NULL COMMENT '主键编号',
  `authority_id` int(11) DEFAULT NULL COMMENT '权限ID',
  `role_id` int(11) DEFAULT NULL COMMENT '角色ID',
  `remark` varchar(200)  COMMENT '备注',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
  PRIMARY KEY (`id`) USING BTREE
) COMMENT = '角色-权限关系表';

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键编号',
  `real_name` varchar(25)  COMMENT '真实姓名',
  `log_name` varchar(25)  COMMENT '登录名',
  `log_pwd` varchar(64)  COMMENT '密码',
  `gender` int(12) DEFAULT NULL COMMENT '性别,0,1男',
  `disabled` int(255) DEFAULT 1 COMMENT '是否禁用,1启用,0禁用',
  `remark` varchar(200)  COMMENT '备注',
  `create_time` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
  PRIMARY KEY (`id`) USING BTREE
) COMMENT = '用户';

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, '刘颖', 'admin', 'admin', 0, 1, NULL, '2020-01-03 14:11:32');

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键编号',
  `user_id` int(11) DEFAULT NULL COMMENT '用户ID',
  `role_id` int(11) DEFAULT NULL COMMENT '角色ID',
  `remark` varchar(200)  COMMENT '备注',
  `create_time` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
  PRIMARY KEY (`id`) 
) COMMENT = '用户角色关系表';

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1, 1, NULL, '2019-12-14 15:14:20');


spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql:///crm_system?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root

spring.datasource.druid.initial-size=10
spring.datasource.druid.max-active=50

spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.default-property-inclusion=non_null

@Configuration
public class LocalDateTimeSerializerConfig {

    @Value("${spring.jackson.date-format}")
    private String pattern;

    @Bean
    public LocalDateTimeSerializer localDateTimeSerializer() {
        return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(pattern));
    }

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(
        LocalDateTimeSerializer localDateTimeSerializer
    ) {
        
        return builder -> builder.serializerByType(
            LocalDateTime.class, localDateTimeSerializer
        );
    }
}

public interface ISysUserService extends IService<SysUser>, UserDetailsService {}

@Service
@Slf4j
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {
	
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Override
    public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {

        //根据登录名称进行查询
        SysUser user = this.lambdaQuery().eq(SysUser::getLogName, logName).one();

        //根据用户ID查询对应的角色
        List<SysRole> roleList = roleMapper.selectRoleByUserId(user.getId());
        
        //组装权限对象
        List<GrantedAuthority> authorities = new ArrayList<>();
        roleList.forEach(role->{
            authorities.add(new SimpleGrantedAuthority(role.getRoleName()))
        });

        //组装用户对象
        return new User(
            user.getLogName(),
            passwordEncoder.encode(user.getLogPwd()),
            authorities
        );
        
    }
}

@Bean
public PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}

//认证相关  用户名密码
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //基于数据库
    auth.userDetailsService(userService).passwordEncoder(this.passwordEncoder());
}

简化认证组装操作

  • Sys_user 实现 UserDetails 接口,指定用户名,密码,角色集合以及账号状态
  • SysRole 实现 GrantedAuthority 接口,指定角色组装的名称
  • SysRoleMapper 接口中添加一个方法 selectRoleByUserId 用于角色集合查询
  • SysUserMapper 接口中添加一个方法 selectUserByLogName 用于登录查询
public interface SysRoleMapper extends BaseMapper<SysRole> {

    @Select(" select id,role_name,remark,create_time from sys_user u " +
            " join sys_user_role ur on u.id = ur.user_id " +
            " join sys_role r on r.id = ur.role_id " +
            " where u.id = #{id} ")
    List<SysRole> selectRoleByUserId(Integer id);

}
123456789
public interface SysUserMapper extends BaseMapper<SysUser> {

    @Results({
        @Result(id = true,property = "id",column = "id"),
        @Result(property = "roles",column = "id",javaType = List.class,
                many = @Many(select = "org.neuedu.security.mapper.SysRoleMapper.selectRoleByUserId") )
    })
    @Select("select id,real_name,log_name,log_pwd,gender,disabled,remark,create_time from sys_user where log_name =#{logName}  ")
    SysUser selectUserByLogName(String logName);

}

@Service
@Slf4j
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {

    @Override
    public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
        return baseMapper.selectUserByLogName(name);
    }

}

开启方法级权限控制

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
     @Autowired
    private ISysUserService userService;

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    //认证相关  用户名密码
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //基于数据库
        auth.userDetailsService(userService).passwordEncoder(this.passwordEncoder());
    }

    //web资源,静态资源的配置
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/logPage"); //登录请求不加入 security 中
    }

    //授权,请求相关
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated();
        //表单的配置
        http.formLogin()
                .usernameParameter("logName").passwordParameter("logPwd")
            	.loginPage("/logPage").loginProcessingUrl("/login");

        //记住我功能
        http.rememberMe().rememberMeParameter("forgetMe")
            .rememberMeCookieName("forgetMe").tokenValiditySeconds(10);
        //csrf
        http.csrf().disable();
    }

@RestController
@RequestMapping("/sysUser")
public class SysUserController {

    @Autowired
    private ISysUserService userService ;

    @PreAuthorize("hasRole('USER')")
    @GetMapping("/list")
    public ResponseEntity list(){
        return ResponseEntity.data(userService.list());
    }

}

具有权限完成 按钮级别的权限认证

<mapper namespace="org.neuedu.security.mapper.SysAuthorityMapper">

    <select id="selectByUserId" resultType="SysAuthority">
         SELECT distinct a.* FROM sys_authority a
         join sys_role_authority ra on ra.authority_id = a.id
         join sys_role r on r.id = ra.role_id
         join sys_user_role ur on ur.role_id = r.id
         join sys_user u on u.id = ur.user_id
         where u.id = #{userId}
         <if test="type != null">
             and type = #{type}
         </if>
         order by a.id
    </select>

</mapper>

@Service
@Slf4j
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {

    @Autowired
    private SysAuthorityMapper authorityMapper;

    @Override
    public UserDetails loadUserByUsername(String logName) throws UsernameNotFoundException {
        //1. 根据用户名去登录
        SysUser currentUser = this.lambdaQuery().eq(SysUser::getLogName, logName).one();
        if(Objects.isNull(currentUser)){
            throw new UsernameNotFoundException("用户名或密码输入有误~");
        }
        //2. 查询权限  type = 1 , 0
        List<SysAuthority> authorities = authorityMapper.selectByUserId(currentUser.getId(), null);
        currentUser.setAuthorities(authorities);
        return currentUser;
    }
}

@PreAuthorize("hasAuthority('user:list')")
@GetMapping("/list")
@ResponseBody
public ResponseEntity list(){
    return ResponseEntity.data(userService.list());
}

@PreAuthorize("hasAuthority('user:del')")
@GetMapping("/del")
public @ResponseBody ResponseEntity del(){
    return ResponseEntity.ok("删除");
}

前后端分离
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private ISysUserService userService;

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    //认证相关  用户名密码
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //基于数据库
        auth.userDetailsService(userService).passwordEncoder(this.passwordEncoder());
    }

    //web资源,静态资源的配置
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/sysUser/logPage");
    }

    //授权,请求相关
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                .anyRequest().authenticated();
        //表单的配置
        http.formLogin()
                .usernameParameter("logName").passwordParameter("logPwd").loginProcessingUrl("/login")
                .successHandler((httpServletRequest, httpServletResponse, authentication) -> {
                    ResponseEntity responseEntity = ResponseEntity.ok("认证成功!");
                    responseJSON(httpServletResponse, responseEntity);
                })
                .failureHandler((httpServletRequest, httpServletResponse, e) -> {
                    ResponseEntity responseEntity = null;
                    if(e instanceof UsernameNotFoundException || e instanceof BadCredentialsException){
                        responseEntity = ResponseEntity.exception(ResponseCode.USERNAME_PASSWORD_EXCEPTION);
                    }else if(e instanceof DisabledException){
                        responseEntity = ResponseEntity.exception(ResponseCode.ACCOUNT_DISABLED);
                    }else{
                        responseEntity = ResponseEntity.exception(ResponseCode.SYSTEM_EXCEPTION);
                    }
                    responseJSON(httpServletResponse,responseEntity);
                })
                .and()
                .exceptionHandling()
                .authenticationEntryPoint((httpServletRequest, httpServletResponse, e) -> {
                    ResponseEntity responseEntity = ResponseEntity.exception(ResponseCode.NEED_LOGIN);
                    responseJSON(httpServletResponse,responseEntity);
                })
                .accessDeniedHandler((httpServletRequest, httpServletResponse, e) -> {
                    ResponseEntity responseEntity = ResponseEntity.exception(ResponseCode.AUTHORIZE_EXCEPTION);
                    responseJSON(httpServletResponse,responseEntity);
                });
        //注销
        http.logout()
                .logoutUrl("/logout").invalidateHttpSession(true)
                .logoutSuccessHandler((httpServletRequest, httpServletResponse, authentication) -> {
                    ResponseEntity responseEntity = ResponseEntity.ok("注销成功~");
                    responseJSON(httpServletResponse,responseEntity);
                });

        //记住我功能
        http.rememberMe().rememberMeParameter("forgetMe").rememberMeCookieName("forgetMe").tokenValiditySeconds(10);
        //csrf
        http.csrf().disable();
    }


    private void responseJSON(HttpServletResponse httpServletResponse, ResponseEntity responseEntity) throws IOException {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        PrintWriter out = httpServletResponse.getWriter();
        try {
            out.write(JsonUtil.ToStringIgnoreNull(responseEntity));
        } catch (Exception e) {
            System.out.println("JSON序列化错误");
        }
        out.close();
    }

}

前后端分离,提取认证对象
@Component
public class SecurityAuthorizeHandler implements AuthenticationSuccessHandler, AuthenticationFailureHandler, AccessDeniedHandler, AuthenticationEntryPoint, LogoutSuccessHandler {

    private  ResponseEntity responseEntity;

    //认证成功
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        responseEntity = ResponseEntity.ok("认证成功!");
        responseJSON(httpServletResponse, responseEntity);
    }

    //认证失败
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        if(e instanceof UsernameNotFoundException || e instanceof BadCredentialsException){
            responseEntity = ResponseEntity.exception(ResponseCode.USERNAME_PASSWORD_EXCEPTION);
        }else if(e instanceof DisabledException){
            responseEntity = ResponseEntity.exception(ResponseCode.ACCOUNT_DISABLED);
        }else{
            responseEntity = ResponseEntity.exception(ResponseCode.SYSTEM_EXCEPTION);
        }
        responseJSON(httpServletResponse,responseEntity);
    }

    //403权限不足
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        responseEntity = ResponseEntity.exception(ResponseCode.AUTHORIZE_EXCEPTION);
        responseJSON(httpServletResponse,responseEntity);

    }

    //非法访问
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        responseEntity = ResponseEntity.exception(ResponseCode.NEED_LOGIN);
        responseJSON(httpServletResponse,responseEntity);

    }

    //注销成功
    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        responseEntity = ResponseEntity.ok("注销成功~");
        responseJSON(httpServletResponse,responseEntity);
    }

    //Response JSON 响应
    private void responseJSON(HttpServletResponse httpServletResponse, ResponseEntity responseEntity) throws IOException {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        PrintWriter out = httpServletResponse.getWriter();
        try {
            out.write(JsonUtil.ToStringIgnoreNull(responseEntity));
        } catch (Exception e) {
            System.out.println("JSON序列化错误");
        }
        out.close();
    }
}

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private ISysUserService userService;

    @Autowired
    private SecurityAuthorizeHandler authorizeHandler;

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    //认证相关  用户名密码
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //基于数据库
        auth.userDetailsService(userService).passwordEncoder(this.passwordEncoder());
    }

    //web资源,静态资源的配置
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/sysUser/logPage");
    }

    //授权,请求相关
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                .anyRequest().authenticated();
        //表单的配置
        http.formLogin()
                .usernameParameter("logName").passwordParameter("logPwd").loginProcessingUrl("/login")
                .successHandler(authorizeHandler)
                .failureHandler(authorizeHandler)
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(authorizeHandler)
                .accessDeniedHandler(authorizeHandler);
        //注销
        http.logout()
                .logoutUrl("/logout").invalidateHttpSession(true)
                .logoutSuccessHandler(authorizeHandler);

        //记住我功能
        http.rememberMe().rememberMeParameter("forgetMe").rememberMeCookieName("forgetMe").tokenValiditySeconds(10);
        //csrf
        http.csrf().disable();
    }

}

ajax请求:

function login(){
    $.ajax({
        method:'post',
        url:'/login',
        dataType:'text',  //需要注意返回数据类型,否则可能JSON解析失败,会进入error
        data:{logName:'user',logPwd:'user'},
        success:function (res) {
            console.log('succ');
            console.log(res);
        },
        error:function (res) {
            console.log('error');
            console.log(res);
        }
    });
}
  • 19
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SSM使用Spring Security进行权限控制的步骤如下: 1. 引入Spring Security依赖: ``` <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>5.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>5.2.2.RELEASE</version> </dependency> ``` 2. 配置Spring Security: 创建一个继承自WebSecurityConfigurerAdapter的配置类,重写configure方法,配置Spring Security的相关信息,如登录页面、登录成功后的处理、权限控制等。 ``` @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private DataSource dataSource; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") // 指定登录页面 .defaultSuccessUrl("/index") // 登录成功后的处理 .permitAll() .and() .logout() .permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(new BCryptPasswordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ``` 3. 配置用户信息: 在上述配置类注入一个UserDetailsService的实现类,用于查询用户信息,同时需要配置一个PasswordEncoder,用于对用户密码进行加密。 ``` @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserDao userDao; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userDao.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("用户名不存在"); } List<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority(user.getRole())); return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities); } } ``` 4. 配置权限控制: 在上述配置类使用antMatchers指定需要进行权限控制的URL以及需要的角色。 ``` @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private DataSource dataSource; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasRole("USER") .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .defaultSuccessUrl("/index") .permitAll() .and() .logout() .permitAll(); } // ... } ``` 以上就是在SSM使用Spring Security进行权限控制的基本步骤。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值