day03_SSM综合案例
一、课程目标
1. 【理解】SpringSecurity权限框架 2. 【掌握】使用SpringSecurity进行用户登录 3. 【掌握】资源权限管理
二、SpringSecurity
2.1 什么是SpringSecurity
Spring Security 的前身是 Acegi Security ,是 Spring 项目组中用来提供安全认证服务的框架。
(https://projects.spring.io/spring-security/) Spring Security 为基于J2EE企业应用软件提供了全面安全服务。特别是使用领先的J2EE解决方案-Spring框架开发的企业软件项目。人们使用Spring Security有很多种原因,不过通常吸引他们的是在J2EE Servlet规范或EJB规范中找不到典型企业应用场景的解决方案。特别要指出的是他们不能再
WAR 或 EAR 级别进行移植。这样,如果你更换服务器环境,就要,在新的目标环境进行大量的工作,对你的应用
系统进行重新配置安全。使用Spring Security 解决了这些问题,也为你提供很多有用的,完全可以指定的其他安
全特性。安全包括两个主要操作。
- “认证”,是为用户建立一个他所声明的主体。主体一般是指用户,设备或可以在你系统中执行动作的其他系
统。(可以将主体当前权限框架自己的session,认证其实就是登录操作,并将登录成功的数据信息存入主体)
- “授权”,指的是一个用户能否在你的应用中执行某个操作,在到达授权判断之前,身份的主题已经由身份验证
过程建立了。(查询是否对应权限,授权其实就是在认证之后请求需要权限的资源时,查询数据库在主体中保存对应权限数据)
这些概念是通用的,不是Spring Security特有的。在身份验证层面,Spring Security广泛支持各种身份验证模式,
这些验证模型绝大多数都由第三方提供,或则正在开发的有关标准机构提供的,例如 Internet Engineering Task
Force.作为补充,Spring Security 也提供了自己的一套验证功能。
Spring Security 目前支持认证一体化如下认证技术:
HTTP BASIC authentication headers (一个基于IEFT RFC 的标准)
HTTP Digest authentication headers (一个基于IEFT RFC 的标准)
HTTP X.509 client certi?cate exchange(一个基于IEFT RFC 的标准)
LDAP (一个非常常见的跨平台认证需要做法,特别是在大环境)
Form-based-authentication (提供简单用户接口的需求)
OpenID authentication Computer Associates Siteminder JA-SIG Central Authentication Service (CAS,这是一个流行的开源单点登录系统)
Transparent authentication context propagation for Remote Method Invocation and HttpInvoker (一个Spring远程调用协议)
2.2 核心组件
SecurityContextHolder
SecurityContextHolder它持有的是安全上下文 (security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权等等,这些都被保存在SecurityContextHolder中。SecurityContextHolder默认使用ThreadLocal 策略来存储认证信息。看到 ThreadLocal 也就意味着,这是一种与线程绑定的策略。在web环境下,Spring Security在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息
看源码他有静态方法
//获取 上下文 public static SecurityContext getContext() { return strategy.getContext(); } //清除上下文 public static void clearContext() { strategy.clearContext(); }
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
getAuthentication() 返回了认证信息,
getPrincipal() 返回了身份信息
UserDetails 便是Spring对身份信息封装的一个接口
SecurityContext
安全上下文,主要持有 Authentication 对象,如果用户未鉴权,那Authentication对象将会是空的。看源码可知
package org.springframework.security.core.context; import java.io.Serializable; import org.springframework.security.core.Authentication; public interface SecurityContext extends Serializable { Authentication getAuthentication(); void setAuthentication(Authentication var1); }
Authentication
鉴权对象,该对象主要包含了用户的详细信息 (UserDetails) 和用户鉴权时所需要的信息,如用户提交的用户名密码、Remember-me Token,或者digest hash值等,按不同鉴权方式使用不同的 Authentication 实现,看源码可知道
package org.springframework.security.core; import java.io.Serializable; import java.security.Principal; import java.util.Collection; public interface Authentication extends Principal, Serializable { Collection<? extends GrantedAuthority> getAuthorities(); Object getCredentials(); Object getDetails(); Object getPrincipal(); boolean isAuthenticated(); void setAuthenticated(boolean var1) throws IllegalArgumentException; }
注意 GrantedAuthority 该接口表示了当前用户所拥有的权限(或者角色)信息。这些信息由授权负责对象 AccessDecisionManager 来使用,并决定最终用户是否可以访问某 资源(URL或方法调用或域对象)。鉴权时并不会使用到该对象
UserDetails
这个接口规范了用户详细信息所拥有的字段,譬如用户名、密码、账号是否过期、是否锁定等。在Spring Security中,获取当前登录的用户的信息,一般情况是需要在这个接口上面进行 扩展,用来对接自己系统的用户,看源码可知
package org.springframework.security.core.userdetails; import java.io.Serializable; import java.util.Collection; import org.springframework.security.core.GrantedAuthority; public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled(); }
UserDetailsService
这个接口只提供一个接口 loadUserByUsername(String username) ,这个接口非常 重要, 一般情况我们都是通过 扩展 这个接口来显示获取我们的用户信息,用户登陆时传递的用户名和密码也是通过这里这查找出来的用户名和密码进行校验,但是真正的校验不在这里,而是由 AuthenticationManager 以及 AuthenticationProvider 负责的,需要强调的是,如果用户不存在,不应返回 NULL,而要抛出异常 UsernameNotFoundException,看源码可知
package org.springframework.security.core.userdetails; public interface UserDetailsService { UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException; }
2.4 SpringSecurity快速入门
创建测试web工程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xzA7lml2-1666446103170)(assets/image-20201011212908786.png)]
导入依赖
<dependencies> <!--springMVC--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.9.RELEASE</version> </dependency> <!--servlet--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!--jsp--> <dependency> <groupId>javax.servlet</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> <!--jstl--> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!--jsckson --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.9</version> </dependency> <!--文件上传--> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency> <!--spring相关坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.2.9.RELEASE</version> </dependency> <!--mybatis相关坐标--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--数据库连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.15</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <!--spring-mybatis坐标--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> </dependency> <!--分页插件--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.10</version> </dependency> <!--junit单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!--spring-security--> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>javax.annotation</groupId> <artifactId>jsr250-api</artifactId> <version>1.0</version> </dependency> <!--log4j依赖--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies>
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-security.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <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> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> </web-app>
spring-security.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:security="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <security:http auto-config="true" use-expressions="false" > <!-- intercept-url定义一个过滤规则pattern表示对哪些url进行权限控制,ccess属性表示在请求对应的URL时需要什么权限, 默认配置时它应该是一个以逗号分隔的角色列表,请求的用户只需拥有其中的一个角色就能成功访问对应的URL--> <security:intercept-url pattern="/**" access="ROLE_USER" /> <!--<security:form-login />--> </security:http> <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> </beans>
测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IwzUCZDP-1666446103172)(assets/image-20201011214626650.png)]
2.5 SpringSecurity 使用自定义页面
添加以下页面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kW5BN7cV-1666446103173)(assets/image-20201011215620167.png)]
以上的文件资料已经在今天的资料中!!!
修改SpringSecurity.xml配置文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dEedyZZa-1666446103174)(assets/image-20201011215753776.png)]
<!-- 配置不过滤的资源(静态资源及登录相关) --> <security:http security="none" pattern="/login.html" /> <security:http security="none" pattern="/failer.html" /> <security:http auto-config="true" use-expressions="false" > <!-- 配置资料连接,表示任意路径都需要ROLE_USER权限 --> <security:intercept-url pattern="/**" access="ROLE_USER" /> <!-- 自定义登陆页面,login-page 自定义登陆页面 authentication-failure-url 用户权限校验失败之后才会跳转到这个页面,如果数据库中没有这个用户则不会跳转到这个页面。 default-target-url 登陆成功后跳转的页面。 注:登陆页面用户名固定 username,密码 password,action:login --> <security:form-login login-page="/login.html" login-processing-url="/login" username-parameter="username" password-parameter="password" authentication-failure-url="/failer.html" default-target-url="/success.html" authentication-success-forward-url="/success.html" /> <!-- 关闭CSRF,默认是开启的,这个如果不关闭会报403的错误. --> <security:csrf disabled="true" /> </security:http> <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>
三、 用户登录
3.1 表结构分析与创建
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pl1HKrLY-1666446103174)(assets/image-20210516220645141.png)]
-- 用户表 CREATE TABLE users( id VARCHAR(32) PRIMARY KEY, email VARCHAR(50) UNIQUE NOT NULL, username VARCHAR(50), PASSWORD VARCHAR(100), phoneNum VARCHAR(20), STATUS INT ); -- 角色表 CREATE TABLE role( id VARCHAR(32) PRIMARY KEY, roleName VARCHAR(50) , roleDesc VARCHAR(50) ); -- 用户角色关联表 CREATE TABLE users_role( userId VARCHAR(32), roleId VARCHAR(32), PRIMARY KEY(userId,roleId), FOREIGN KEY (userId) REFERENCES users(id), FOREIGN KEY (roleId) REFERENCES role(id) ); -- 资源权限表 CREATE TABLE permission( id VARCHAR(32) PRIMARY KEY, permissionName VARCHAR(50) , url VARCHAR(50) ); -- 角色权限关联表 CREATE TABLE role_permission( permissionId VARCHAR(32), roleId VARCHAR(32), PRIMARY KEY(permissionId,roleId), FOREIGN KEY (permissionId) REFERENCES permission(id), FOREIGN KEY (roleId) REFERENCES role(id) );
3.2 创建实体类
创建Users
@Data @NoArgsConstructor public class Users{ private int id; private String email; private String username; private String password; private String phoneNum; private int status;// '状态0 未开启 1 开启' private String statusStr; private Role role; public String getStatusStr() { if(status==0){ statusStr="未开启"; }else if(status==1){ statusStr="开启"; } return statusStr; } }
创建Role
@Data @NoArgsConstructor public class Role { private int id; private String roleName; private String roleDesc; private List<Permission> permissions; }
创建Permission
@Data @NoArgsConstructor public class Permission { private int id; private String permissionName; private String url; }
3.3 Spring Security使用数据库认证
在Spring Security中如果想要使用数据进行认证操作,有很多种操作方式,这里我们介绍使用UserDetails、
UserDetailsService来完成操作。
- UserDetails
public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled(); }
- UserDetails是一个接口,我们可以认为UserDetails作用是于封装当前进行认证的用户信息,但由于其是一个接口,所以我们可以对其进行实现,也可以使用SpringSecurity提供的一个UserDetails的实现类User来完成操作。
以下是User类的部分代码
public class User implements UserDetails, CredentialsContainer { private String password; private final String username; private final Set authorities; private final boolean accountNonExpired; //帐户是否过期 private final boolean accountNonLocked; //帐户是否锁定 private final boolean credentialsNonExpired; //认证是否过期 private final boolean enabled; //帐户是否可用 }
* **UserDetailsService** ```java public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }
上面将UserDetails与UserDetailsService做了一个简单的介绍,那么我们具体如何完成Spring Security的数据库认证操作哪,我们通过用户管理中用户登录来完成Spring Security的认证操作。
3.4 用户登录流程分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SaJwwz9m-1666446103175)(assets/image-20201012180237425.png)]
3.5 代码编写
编写login和failer页面
login和failer页面已经在资料中,直接复制到webapp的根目录下即可!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TPgLkZ1C-1666446103176)(assets/image-20201015152415708.png)]
导入spring security相关坐标
<!--spring-security--> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>5.2.9.RELEASE</version> </dependency>
配置web.xml
<!--加载spring环境 和 spring-security配置文件--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext*.xml,classpath:spring/spring-security.xml</param-value> </context-param> <!-- 额外添加springSecurity过滤器 --> <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>
配置spring-security.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:security="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <!-- 配置不拦截的页面与静态资源 --> <security:http pattern="/login.jsp" security="none"/> <security:http pattern="/failer.jsp" security="none"/> <security:http pattern="/css/**" security="none"/> <security:http pattern="/img/**" security="none"/> <security:http pattern="/layui/**" security="none"/> <!-- 配置具体的规则 auto-config="true" 不用自己编写登录的页面,框架提供默认登录页面 use-expressions="false" 是否使用SPEL表达式 --> <security:http auto-config="true" use-expressions="false"> <!-- 同源策略 如果页面使用iframe需要配置 否则不能使用 --> <security:headers> <security:frame-options disabled="true"/> </security:headers> <!-- 配置具体的拦截的规则 pattern="请求路径的规则" access="访问系统的人,必须有ROLE_USER的角色" --> <security:intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN" /> <!-- 定义跳转的具体的页面 --> <security:form-login login-page="/login.jsp" login-processing-url="/login" default-target-url="/index.jsp" authentication-failure-url="/failer.jsp" authentication-success-forward-url="/index.jsp" /> <!-- 关闭跨域请求 --> <security:csrf disabled="true"/> <!-- 退出 --> <security:logout invalidate-session="true" logout-url="/logout" logout-success-url="/login.jsp" /> </security:http> <!-- 切换成数据库中的用户名和密码 --> <security:authentication-manager> <!-- user-service-ref="userService" springSecurity用于获取账号信息的类 需要实现UserDetailsService 重写加载数据方法 --> <security:authentication-provider user-service-ref="userService"> <!-- 配置加密的方式 注意如果使用需要开启下面配置的加密类 <security:password-encoder ref="passwordEncoder"/>--> </security:authentication-provider> </security:authentication-manager> <!-- 配置加密类 --> <!--<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>--> <!-- 提供了入门的方式,在内存中存入用户名和密码 <security:authentication-manager> <security:authentication-provider> <security:user-service> <security:user name="admin" password="{noop}admin" authorities="ROLE_USER"/> </security:user-service> </security:authentication-provider> </security:authentication-manager> --> </beans>
spring security接收到用户名之后,spring security怎么就知道我们要调用的是那个service来完成用户的查询操作呢?
在配置文件中有这么一段配置:
<security:authentication-manager> <security:authentication-provider user-service-ref="userService"> <!-- 配置加密的方式 <security:password-encoder ref="passwordEncoder"/>--> </security:authentication-provider> </security:authentication-manager>
user-service-ref
属性就是来指定要执行的service,当然这个service来继承 UserDetailsService,扩展我们自己的service接口,完成用户的认证的操作
编写UserMapper
public interface UserMapper { //使用权限框架操作 根据账号查询信息 @Select("select * from users where username=#{username} and status=1") public Users selectByUserName(String username); }
编写UserService
public interface UserService extends UserDetailsService { }
@Service("userService") public class UserServiceImpl implements UserService { @Autowired UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //查询数据返回实体类对应数据 Users users = userMapper.selectByUserName(username); if(users!=null){ //创建springSecurity主体中存储的账号对象并返回(账号,密码,权限列表) //权限列表这里填null注意 UserDetails userDetails=new User(users.getUsername(),users.getPassword(),null); return userDetails; } return null; } }
测试
在登录页面输入用户名 密码 跳转到了错误页面:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-myoRq1iw-1666446103177)(assets/image-20220525121742478.png)]
四、 用户登录问题分析
问题一:登录失败的问题
在数据库中是有jack这个用户的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f9gasAmT-1666446103178)(assets/image-20201015170433128.png)]
为什么还是没办法登录,原因是在于我们的spring-security.xml配置文件中配置的问题:
在配置文件中,我们配置了
<!-- 配置具体的拦截的规则 pattern="请求路径的规则" access="访问系统的人,必须有ROLE_USER的角色" --> <security:intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN"/>
spring security 拦截了所有的请求,要想登录认证必须是有ROLE_USER,ROLE_ADMIN
两个权限的,在UserServiceImpl类中我将查询到的UserInfo
中的信息封装到了User
对象中,有一个值是为null
User user = new User(userInfo.getUsername(),userInfo.getPassword(), null);
也就是说我们查询出的这个对象是没有权限的,既是用户名和密码对了,没有权限也是没有办法登录的!
在这里我们需要模拟给出ROLE_USER,ROLE_ADMIN
两个权限,修改UserServiceImpl类
@Service("userService") public class UserServiceImpl implements UserService { @Autowired UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //查询数据返回实体类对应数据 Users users = userMapper.selectByUserName(username); if(users!=null){ //创建springSecurity主体中存储的账号对象并返回(账号,密码,权限列表) UserDetails userDetails=new User(users.getUsername(),users.getPassword(),getAuthority()); return userDetails; } return null; } public List<SimpleGrantedAuthority> getAuthority(){ ArrayList<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>(); simpleGrantedAuthorities.add(new SimpleGrantedAuthority("ROLE_USER")); simpleGrantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN")); return simpleGrantedAuthorities; } }
修改完毕之后我们再次测试,看是否能登录成功!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EBq1kGou-1666446103180)(assets/image-20201015172905442.png)]
这个报错的原因是spring security 默认使用的是密文提交的,现在没有进行加密! 我们只需要在获取密码的前面加上"{noop}" 代表的是使用明文
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZFX4KXjq-1666446103180)(assets/image-20201015205108314.png)]
此时就认证成功
问题二:数据库查询用户角色
在上面的代码中我们的用户角色是自己手动添加的,这不是通用的一种方式,用户的角色我们要从数据库中进行查询!
修改UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.yanqi.ssm.mapper.UserMapper"> <!--通过用户名查询用户--> <resultMap id="userResultMap" type="UserInfo" autoMapping="true"> <id property="id" column="id" /> <collection property="roles" ofType="Role" javaType="List" autoMapping="true"> <id property="id" column="rid" /> </collection> </resultMap> <select id="findUserByUserName" resultMap="userResultMap"> SELECT * , r.id rid FROM users u, role r, users_role ur WHERE u.id = ur.userId AND r.id = ur.roleId AND u.username = #{username} </select> </mapper>
修改UserService
在这里需要注意的是,获取到对应的角色之后,其实还是不能登录,在users表中,有一个字段status
,这个字段表示的用户是否可能,0表示可用 1表示不可用。此时的功能原来使用的构造已经无法满足了,我们需要使用另外一个构造
public User(String username, String password, /** boolean enabled: 账户是否可用 boolean accountNonExpired:账户是否过期 boolean credentialsNonExpired:密码是否过期 boolean accountNonLocked:账户是否冻结被锁定 authorities:该账户所具有的权限 */ boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) { }
完整代码实现
@Service("userService") public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { UserInfo userInfo = userMapper.findUserByUserName(s); User user = new User(userInfo.getUsername(), "{noop}"+userInfo.getPassword(), userInfo.getStatus() == 0 ? false : true , true,true,true, getAuthority(userInfo.getRoles())); return user; } public List<SimpleGrantedAuthority> getAuthority(List<Role> roles){ ArrayList<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>(); for (Role role : roles) { simpleGrantedAuthorities.add(new SimpleGrantedAuthority("ROLE_"+role.getRoleName())); } return simpleGrantedAuthorities; } }
五、权限管理
5.1 查询权限
编写PermissionMapper接口
public interface PermissionMapper { //查询所有权限数据 @Select("select * from permission") public List<Permission> selectAll(); }
编写PermissionService
public interface PermissionService { //查询所有权限数据 public List<Permission> findAll(); }
@Service("permissionService") public class PermissionServiceImpl implements PermissionService { @Autowired PermissionMapper permissionMapper; @Override public List<Permission> findAll() { return permissionMapper.selectAll(); } }
编写PermissionController
@Controller @RequestMapping("/permission") public class PermissionController { @Autowired PermissionService permissionService; //查询所有数据 @RequestMapping("/findAll") public String findAll(HttpServletRequest request, @RequestParam(value = "page",required = false,defaultValue = "1") int page, @RequestParam(value = "limit",required = false,defaultValue = "5")int limit){ PageHelper.startPage(page,limit); List<Permission> all = permissionService.findAll(); PageInfo<Permission> pageInfo=new PageInfo<>(all); request.setAttribute("pageInfo",pageInfo); return "/permission/permission-list"; } }
测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hq3GBsyg-1666446103181)(assets/image-20220525145959457.png)]
5.2 添加权限
编写PermissionMapper接口
// 添加权限数据 @Insert("insert into permission (permissionName,url) values(#{permissionName},#{url})") public int insert(Permission permission);
编写PermissionService
//添加权限数据 public int add(Permission permission);
/** * 添加资源权限 * @param permission */ @Override public int add(Permission permission) { return permissionMapper.insert(permission); }
编写PermissionController
@Controller @RequestMapping("permission") public class PermissionController { @Autowired private PermissionService permissionService; /** * 添加资源权限 * @param permission * @return * @throws Exception */ @RequestMapping(value = "/add",method = RequestMethod.POST) public String add(HttpServletRequest request,Permission permission){ permissionService.add(permission); return "redirect:/permission/findAll"; } }
测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MjJtEe1l-1666446103182)(assets/image-20220525145939176.png)]
5.3 删除权限
独立完成,注意删除域角色关联表对应的资源权限信息
编写PermissionMapper接口
// 根据id删除权限数据 @Delete("delete from permission where id=#{id}") public int deleteById(int id);
编写PermissionService
//根据id删除权限数据 public int delete(int id);
/** * 删除资源权限 * @param id */ @Override public int delete(int id) { return permissionMapper.deleteById(id); }
编写PermissionController
@Controller @RequestMapping("permission") public class PermissionController { @Autowired private PermissionService permissionService; /** * 删除资源权限 * @param id * @return * @throws Exception */ @RequestMapping("/deleteById") public String deleteById(HttpServletRequest request,int id){ permissionService.delete(id); return "redirect:/permission/findAll"; } }
rmissionService.add(permission); return “redirect:/permission/findAll”; } }
### 测试 [外链图片转存中...(img-MjJtEe1l-1666446103182)] ## 5.3 删除权限 独立完成,注意删除域角色关联表对应的资源权限信息 ### 编写PermissionMapper接口 ```java // 根据id删除权限数据 @Delete("delete from permission where id=#{id}") public int deleteById(int id);
编写PermissionService
//根据id删除权限数据 public int delete(int id);
/** * 删除资源权限 * @param id */ @Override public int delete(int id) { return permissionMapper.deleteById(id); }
编写PermissionController
@Controller @RequestMapping("permission") public class PermissionController { @Autowired private PermissionService permissionService; /** * 删除资源权限 * @param id * @return * @throws Exception */ @RequestMapping("/deleteById") public String deleteById(HttpServletRequest request,int id){ permissionService.delete(id); return "redirect:/permission/findAll"; } }
03SSM综合案例之16SpringSecurity(二)https://developer.aliyun.com/article/1432884