自定义表单mysql_Spring Security(一)自定义表单及认证授权(整合mybatis generator和mybatis)...

本文介绍了如何在Spring Boot项目中自定义Spring Security的表单登录页面和处理逻辑,以及如何实现数据库模型的认证和授权。通过自定义WebSecurityConfig配置,实现了登录页跳转、JSON响应、登录成功与失败处理。此外,还展示了如何使用MySQL创建用户表,结合MyBatis Generator生成代码,并将Spring Boot与MyBatis整合,实现用户数据的CRUD操作。最后,通过实现UserDetails和UserDetailsService接口,完成了数据库中的用户信息与Spring Security的集成,实现了基于数据库的认证和授权流程。
摘要由CSDN通过智能技术生成

目录:

1、默认表单认证  

创建 springboot 项目,依赖:

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-security

写一个测试 controller

@RestController

@RequestMapping("/index")public classIndexController {

@RequestMapping("/test1")publicString test1(String name, Integer age) {return "test1";

}

}

启动项目,访问 http://localhost:8089/BootDemo/index/test1,弹出默认表单认证

e3abfe9bbdd7ca9b4d699aaf206eceb2.png

默认用户名为 user, 密码是动态生成并打印到控制台的一窜随机码。当然,用户名和密码可以在application.properties 中配置

spring.security.user.name=test

spring.security.user.password=123

2、自定义表单登陆页  

虽然spring security 自带的表单登陆页可以方便快速地启动,但大多数应用程序更希望提供自己的的表单登陆页,此时就需要自定义表单登陆页。

d2930de79a15c654b3226fd62a2690f9.png

WebSecurityConfig

packagecom.oy;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.EnableWebSecurity;importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecuritypublic class WebSecurityConfig extendsWebSecurityConfigurerAdapter {

@Overrideprotected void configure(HttpSecurity http) throwsException {

http.authorizeRequests().anyRequest().authenticated()

.and().formLogin().loginPage("/mylogin.html")

.loginProcessingUrl("/login") //指定处理登陆请求的路径

.permitAll() // 登陆页和 "/login" 不设置权限

.and().csrf().disable();

}

}

表单登陆页

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

Insert title here

自定义表单登陆页

用户名:
密   码:

View Code

启动项目,访问 localhost:8089/BootDemo/index/test1,自动跳转到登陆页(浏览器地址为 http://localhost:8089/BootDemo/mylogin.html)。

输入test/123, 登陆成功,拿到响应结果:

10c11e51ec931f82bb3e30b36ff27298.png

如果输入错误的用户名或密码,响应结果(状态码 302,重定向到登陆页)

856cbbc44af5fc90e5d272b4c847a0b2.png

对现在前后端分离的项目而言,重定向不在需要后端做,后端一般返回 json 数据,告知前端登陆成功与否,由前端决定如何处理后续逻辑,而非由服务器主动执行页面跳转。这在 Spring Security 中同样可以实现。

@EnableWebSecuritypublic class WebSecurityConfig extendsWebSecurityConfigurerAdapter {

@Overrideprotected void configure(HttpSecurity http) throwsException {

http.authorizeRequests().anyRequest().authenticated().and()

.formLogin().loginPage("/mylogin.html")

.loginProcessingUrl("/login") //指定处理登陆请求的路径//指定登陆成功时的处理逻辑

.successHandler(newAuthenticationSuccessHandler() {

@Overridepublic voidonAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,

Authentication authentication)throwsIOException, ServletException {

response.setContentType("application/json;charset=utf-8");

response.getWriter().write("{\"code\":0, \"data\":{}}");

}

})//指定登陆失败时的处理逻辑

.failureHandler(newAuthenticationFailureHandler() {

@Overridepublic voidonAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,

AuthenticationException exception)throwsIOException, ServletException {

response.setContentType("application/json;charset=utf-8");

response.setStatus(401);

response.getWriter().write("{\"code\":0, \"msg\":\"用户名或密码错误\"}");

}

})

.permitAll().and()

.csrf().disable();

}

}

其中,successHandler()方法带有一个 Authentication 参数,携带当前登陆用户名及其角色等信息;而 failureHandler() 方法携带一个AuthenticationException 异常参数。

3、自定义数据库模型的认证和授权  

前面沿用了 Spring Security 默认的安全机制:仅有一个用户,仅有一种角色。在实际开发中,这自然是无法满足要求的。

编写三个 controller 进行测试,其中 /admin/api 下的内容是系统后台管理相关的API,必须拥有管理员权限(具有 "admin" 角色)才能访问; /user/api 必须在用户登陆并且具有 “user” 角色才能访问。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

@RestController

@RequestMapping("/admin/api")public classAdminController {

@GetMapping("/hello")publicString hello() {return "hello, admin";

}

}

@RestController

@RequestMapping("/user/api")public classUserController {

@GetMapping("/hello")publicString hello() {return "hello, user";

}

}

@RestController

@RequestMapping("/app/api")public classAppController {

@GetMapping("/hello")publicString hello() {return "hello, app";

}

}

View Code

启动项目,访问 http://localhost:8089/BootDemo/user/api/hello,跳转到登陆页面,使用 test/123 登陆后。再次访问 http://localhost:8089/BootDemo/user/api/hello,此时服务器返回 403,表示用户授权失败(401 代表用户认证失败)。

3.1、使用 mysql 创建数据库  

create database security_test charset=utf8;

use security_test;

create table user (

`id` bigint notnullauto_increment,

`username` varchar(100) not null,

`password` varchar(100) not null,

`enable` tinyint notnull default 1 comment '用户是否可用,1:可用,2:禁用',

`roles` varchar(500) comment '角色,多个角色用逗号隔开',

primary key (`id`),

key username (`username`)

);

insert into user(username,password,roles) values('admin','123','ROLE_user,ROLE_admin');

insert into user(username,password,roles) values('user','123','ROLE_user');

3.2、mybatis generator 生成代码  

新建一个普通 Java Project

cd9554e26dcc28d85b85c15c94215465.png

Generator 类

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.oy;importjava.io.File;importjava.util.ArrayList;importjava.util.List;importorg.mybatis.generator.api.MyBatisGenerator;importorg.mybatis.generator.config.Configuration;importorg.mybatis.generator.config.xml.ConfigurationParser;importorg.mybatis.generator.internal.DefaultShellCallback;public classGenerator {public static void main(String[] args) throwsException {

List warnings = new ArrayList();boolean overwrite = true;

File configFile= new File("src/com/oy/generator.xml");

ConfigurationParser cp= newConfigurationParser(warnings);

Configuration config=cp.parseConfiguration(configFile);

DefaultShellCallback callback= newDefaultShellCallback(overwrite);

MyBatisGenerator myBatisGenerator= newMyBatisGenerator(config, callback, warnings);

myBatisGenerator.generate(null);

}

}

View Code

generator.xml

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

/p>

PUBLIC"-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"

"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

View Code

运行 Generator#main(),即可生成代码。

上面的 jar 可以从 maven 仓库下载(建个 maven 工程,jar包下载到本地仓库,手动复制到上面的项目中)

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

org.mybatis

mybatis-spring

1.3.0

org.mybatis

mybatis

3.2.6

mysql

mysql-connector-java

5.1.36

com.qiukeke

mybatis-generator-limit-plugin

1.0.4

View Code

mybatis-generator-limit-plugin-1.0.4.jar 是个 mybatis 分页插件,会在 实体 example 类中添加 limit、offset 两个字段(同时 mapping.xml 文件中也加入了分页功能)

9089b955f1b2cc16a15fbb62b684a8fc.png

3.3、springboot 整合 mybatis  

依赖:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

org.mybatis.spring.boot

mybatis-spring-boot-starter

1.3.2

mysql

mysql-connector-java

5.1.36

View Code

配置:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

#datasource

spring.datasource.driver-class-name=com.mysql.jdbc.Driver

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/security_test?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8

spring.datasource.username=root

spring.datasource.password=123456spring.datasource.tomcat.min-idle=5##################### MyBatis相关配置 [start] #####################

#MyBatis映射文件

mybatis.mapper-locations=classpath:com/oy/mapping/*.xml

#扫描生成实体的别名,需要和注解@Alias联合使用

mybatis.type-aliases-package=com.oy.model

#MyBatis配置文件,当你的配置比较复杂的时候,可 以使用

#mybatis.config-location=

#级联延迟加载。true:开启延迟加载

mybatis.configuration.lazy-loading-enabled=true

#积极的懒加载。false:按需加载

mybatis.configuration.aggressive-lazy-loading=false

##################### MyBatis相关配置 [end] ######################

View Code

在主 springboot 配置类上添加注解 @MapperScan 扫描 dao 接口生成代理对象

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

@SpringBootApplication

@MapperScan("com.oy.dao")public classApplication {public static voidmain(String[] args) {

SpringApplication.run(Application.class, args);

}

}

View Code

写测试代码,进行测试:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

@RestController

@RequestMapping("/app/api")public classAppController {

@AutowiredprivateUserService userService;

@RequestMapping("/{id}")publicString findById(@PathVariable Long id) {

User dbUser=userService.getUserById(id);returnJSONObject.toJSONString(dbUser);

}

@GetMapping("/hello")publicString hello() {return "hello, app";

}

}

@Servicepublic class UserServiceImpl implementsUserService {

@AutowiredprivateUserDao userDao;

@OverridepublicUser getUserById(Long id) {returnuserDao.selectByPrimaryKey(id);

}

}

View Code

访问 http://localhost:8089/BootDemo/app/api/1,结果:

8ad2f98bcd6bc8d029a5ac95834ec14f.png

3.4、实现 UserDetails  

Spring Security 中,使用 UserDetails 来封装用户信息,包含一系列在验证时要用到的信息,比如用户名、密码、权限及其他信息,Spring Security 会根据这些信息来校验。

UserDetails 有这样一些方法:

public interface UserDetails extendsSerializable {

/*** Returns the authorities granted to the user. Cannot return null.

*

*@returnthe authorities, sorted by natural key (never null)*/Collection extends GrantedAuthority>getAuthorities();/*** Returns the password used to authenticate the user.

*

*@returnthe password*/String getPassword();/*** Returns the username used to authenticate the user. Cannot return null.

*

*@returnthe username (never null)*/String getUsername();/*** Indicates whether the user's account has expired. An expired account cannot be

* authenticated.

*

*@returntrue if the user's account is valid (ie non-expired),

* false if no longer valid (ie expired)*/

booleanisAccountNonExpired();/*** Indicates whether the user is locked or unlocked. A locked user cannot be

* authenticated.

*

*@returntrue if the user is not locked, false otherwise*/

booleanisAccountNonLocked();/*** Indicates whether the user's credentials (password) has expired. Expired

* credentials prevent authentication.

*

*@returntrue if the user's credentials are valid (ie non-expired),

* false if no longer valid (ie expired)*/

booleanisCredentialsNonExpired();/*** Indicates whether the user is enabled or disabled. A disabled user cannot be

* authenticated.

*

*@returntrue if the user is enabled, false otherwise*/

booleanisEnabled();

}

为了程序的可维护性,我没有修改 mybatis generator 根据数据库 user 表映射生成的 User 类,而是写一个新类继承 User 类,并实现 UserDetails 接口。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.oy.security;importjava.util.Collection;importjava.util.List;importorg.springframework.security.core.GrantedAuthority;importorg.springframework.security.core.userdetails.UserDetails;importcom.oy.model.User;public class SecurityUser extends User implementsUserDetails {private static final long serialVersionUID = 1L;private Listauthorities;public void setAuthorities(Listauthorities) {this.authorities =authorities;

}/*** getAuthorities() 方法本身对应的是 roles 字段,但由于结构不一样,

* 所以此类中添加一个 authorities 字段,后面自己手动设置*/@Overridepublic Collection extends GrantedAuthority>getAuthorities() {return this.authorities;

}

@Overridepublic booleanisAccountNonExpired() {return true;

}

@Overridepublic booleanisAccountNonLocked() {return true;

}

@Overridepublic booleanisCredentialsNonExpired() {return true;

}

@Overridepublic booleanisEnabled() {return true;

}

@OverridepublicString toString() {return "SecurityUser [id=" + getId() + ", username=" + getUsername() + ", password="

+ getPassword() + ", enable=" + getEnable() + ", roles=" + getRoles() + "]";

}

}

View Code

3.5、实现 UserDetailsService  

UserDetailsService 仅定义了一个 loadUserByUsername() 方法,用于获取一个 UserDetails 对象。UserDetails 对象包含一系列在验证时会用到的信息,包括用户名、密码、权限等。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.oy.security;importjava.util.List;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.core.authority.AuthorityUtils;importorg.springframework.security.core.userdetails.UserDetails;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.core.userdetails.UsernameNotFoundException;importorg.springframework.stereotype.Service;importcom.oy.dao.UserDao;importcom.oy.model.User;importcom.oy.model.UserExample;/***@authoroy

*@version1.0

* @date 2020年4月14日

* @time 上午10:25:02*/@Servicepublic class MyUserDetailsService implementsUserDetailsService {

@AutowiredprivateUserDao userDao;

@Overridepublic UserDetails loadUserByUsername(String username) throwsUsernameNotFoundException {//从数据库尝试获取该用户

UserExample example = newUserExample();

UserExample.Criteria criteria=example.createCriteria();

criteria.andUsernameEqualTo(username);

List userList =userDao.selectByExample(example);if (userList == null || userList.size() == 0) {throw new RuntimeException("该用户不存在");

}

SecurityUser sUser= getUser(userList.get(0));

System.out.println("sUser: " +sUser);//将数据库 roles 字段解析成 UserDetails 的权限集

sUser.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(sUser.getRoles()));returnsUser;

}/*** 读取 User 对象的属性,封装一个 SecurityUser 对象

*@paramuser

*@return

*/

privateSecurityUser getUser(User user) {if (user == null) {throw new RuntimeException("该用户不存在");

}

SecurityUser sUser= newSecurityUser();

sUser.setEnable(user.getEnable());

sUser.setId(user.getId());

sUser.setPassword(user.getPassword());

sUser.setRoles(user.getRoles());

sUser.setUsername(user.getUsername());returnsUser;

}

}

View Code

3.6、其他

1)测试时报 java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null" 这个错误。原因是5.x 版本后默认开启了委派密码编码器,所以本文暂时将密码编码器设置为 noOpPasswordEncoder。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

@BeanpublicPasswordEncoder passwordEncoder() {returnNoOpPasswordEncoder.getInstance();

}

View Code

PasswordEncoder 接口有两个方法

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

public interfacePasswordEncoder {

String encode(CharSequence var1);booleanmatches(CharSequence var1, String var2);

}

View Code

实际开发中,可以使用

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

@Bean

PasswordEncoder passowrdEncoder() {return newBCryptPasswordEncoder();

}

View Code

所以,当用户注册,保存用户的密码时,从 Spring 容器中获取 PasswordEncoder 实例,调用 PasswordEncoder 实例的 encode() 方法对密码进行加密(数据库存的是加密后的密码)。

2)UserDetails 接口中包含的一些方法,比如 isEnabled() 可以用来校验用户状态(是否删除),isAccountNonLocked() 可以用来校验用户状态(是否冻结)等。可以根据业务场景进行实现,比如:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

@Overridepublic booleanisEnabled() {if (getEnable().intValue() == 2) {return false;

}return true;

}

View Code

===================================================================================================

至此,代码写完了。当使用 admin/123 登陆后,再次访问 http://localhost:8089/BootDemo/admin/api/1, 就不会返回 403 了。

总结一下认证和授权过程:

1) 用户使用 admin/123 登陆时,Spring Security 调用 UserDetailsService#loadUserByUsername() 读取数据库,查出是否有 admin 这个用户名,有则读取,并将用户名、密码、权限封装成一个 UserDetails 对象返回。然后,Spring Security 根据UserDetails 对象的密码与表单传来的密码比较。

2) 当访问非公开权限的资源时,调用UserDetails#getAuthorities() 进行权限校验。

本文内容包括:

处理用户信息获取逻辑 UserDetailsService

处理用户校验逻辑 UserDetails

处理密码加密解密 PasswordEncoder

自定义登陆页面

自定义登陆成功处理 AuthenticationSuccessHandler

自定义登陆失败处理 AuthenticationFailureHandler

参考:

1)《Spring Security 实战》-- 陈木鑫

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值