> 文档中心 > spring-security详解

spring-security详解

spring-security详解

随便写一下,增删改查返回对应的字符

import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/security")public class OauthBase {    @GetMapping("/add")    public String add(){ return "add";    }    @GetMapping("/update")    public String update(){ return "update";    }    @GetMapping("/get")    public String get(){ return "get";    }    @GetMapping("/del")    public String del(){ return "del";    }}

基础认证配置类

import org.springframework.context.annotation.Bean;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.crypto.password.NoOpPasswordEncoder;import org.springframework.stereotype.Component;@Component@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    /**     * 新增Security     * 授权账户     * @param auth     * @throws Exception     */    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception { /**  * 账号、密码、接口权限  */ auth.inMemoryAuthentication().withUser("mykt").password("mykt").authorities("/");    }    /**     * 认证方式     * @param http     * @throws Exception     */    @Override    protected void configure(HttpSecurity http) throws Exception { /**  * 认证之后就能访问所有接口  */ http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic();    }    /**     * 加密方式,恢复以前模式     * @return     */    @Bean    public static NoOpPasswordEncoder PasswordEncoder(){ return (NoOpPasswordEncoder)NoOpPasswordEncoder.getInstance();    }}

spring-security详解

输入对应的账号密码就可以访问对应的接口,这个非常简单。

From表单验证

/**     * 认证方式     * @param http     * @throws Exception     */    @Override    protected void configure(HttpSecurity http) throws Exception { /**  * 基础认证  */ //http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic(); /**  * form表单验证  */ http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().formLogin();    }

效果图(账号密码没变,页面自带,不需要自己写)

spring-security详解

配置权限规则(不同的账号对应不同的权限)

import org.springframework.context.annotation.Bean;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.crypto.password.NoOpPasswordEncoder;import org.springframework.stereotype.Component;@Component@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    /**     * 新增Security     * 授权账户     * @param auth     * @throws Exception     */    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception { /**  * 账号、密码、接口权限  */ auth.inMemoryAuthentication().withUser("mykt-admin").password("mykt-admin").authorities("add","update","get","del"); auth.inMemoryAuthentication().withUser("mykt-add").password("mykt-add").authorities("add"); auth.inMemoryAuthentication().withUser("mykt-update").password("mykt-update").authorities("update"); auth.inMemoryAuthentication().withUser("mykt-get").password("mykt-get").authorities("get"); auth.inMemoryAuthentication().withUser("mykt-del").password("mykt-del").authorities("del");    }    /**     * 认证方式     * @param http     * @throws Exception     */    @Override    protected void configure(HttpSecurity http) throws Exception { /**  * 基础认证  */ //http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic(); /**  * form表单验证  */ //http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().formLogin(); /**  * 拦截规则  */ http.authorizeRequests().antMatchers("/add").hasAnyAuthority("add")  .antMatchers("/get").hasAnyAuthority("get")  .antMatchers("/update").hasAnyAuthority("update")  .antMatchers("/del").hasAnyAuthority("del")  //form验证  .antMatchers("/**").fullyAuthenticated().and().formLogin();    }    /**     * 加密方式,恢复以前模式     * @return     */    @Bean    public static NoOpPasswordEncoder PasswordEncoder(){ return (NoOpPasswordEncoder)NoOpPasswordEncoder.getInstance();    }}
创建了五个账号,mykt-admin、mykt-add 、mykt-update 、mykt-get 、mykt-del,那么从账号名称中就可以看出来这些账号对应的权限
mykt-add测试

spring-security详解
spring-security详解
spring-security详解

mykt-admin测试

spring-security详解
spring-security详解
spring-security详解
spring-security详解

权限不足跳转页面
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;import org.springframework.boot.web.server.ErrorPage;import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpStatus;@Configurationpublic class WebServerAutoConfiguration {    @Bean    public ConfigurableServletWebServerFactory webServerFactroy(){ TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); ErrorPage errorPage403 = new ErrorPage(HttpStatus.FORBIDDEN,"/error/403"); ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND,"/error/404"); ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR,"/error/500"); factory.addErrorPages(errorPage403,errorPage404,errorPage500); return  factory;    }}
import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class ErrorController {    @RequestMapping("/error/403")    public String error403(){ return "你当前访问的接口权限不足";    }    @RequestMapping("/error/404")    public String error404(){ return "资源不可用";    }}

spring-security详解
spring-security详解
spring-security详解

为了演示效果, 正常的环境应该是返回码值,由前端对码值进行错误逻辑判断跳转到对应错误页面

自定义登陆页面

创建页面将html文件放在resource文件下面
<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>登陆</title></head><body><h1>自定义登陆页面</h1>    <form action="/login" method="post"> <span>用户名称</span><input type="text" name="username" /><br> <span>用户密码</span><input type="password" name="password" /><br> <input type="submit" value="登陆">    </form></body></html>
指定登陆请求
import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;/** * login请求 */@Controllerpublic class LoginController {    @RequestMapping("/login")    public String login(){ return "login";    }}
import org.springframework.context.annotation.Bean;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.crypto.password.NoOpPasswordEncoder;import org.springframework.stereotype.Component;@Component@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    /**     * 新增Security     * 授权账户     * @param auth     * @throws Exception     */    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception { /**  * 账号、密码、接口权限  */ auth.inMemoryAuthentication().withUser("mykt-admin").password("mykt-admin").authorities("add","update","get","del"); auth.inMemoryAuthentication().withUser("mykt-add").password("mykt-add").authorities("add"); auth.inMemoryAuthentication().withUser("mykt-update").password("mykt-update").authorities("update"); auth.inMemoryAuthentication().withUser("mykt-get").password("mykt-get").authorities("get"); auth.inMemoryAuthentication().withUser("mykt-del").password("mykt-del").authorities("del");    }    /**     * 认证方式     * @param http     * @throws Exception     */    @Override    protected void configure(HttpSecurity http) throws Exception { /**  * 基础认证  */ //http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic(); /**  * form表单验证  */ //http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().formLogin(); /**  * 拦截规则  */ http.authorizeRequests().antMatchers("/add").hasAnyAuthority("add")  .antMatchers("/get").hasAnyAuthority("get")  .antMatchers("/update").hasAnyAuthority("update")  .antMatchers("/del").hasAnyAuthority("del")  .antMatchers("/login").permitAll()  //form验证  .antMatchers("/**").fullyAuthenticated().and().formLogin()  //添加自定义跳转页面  .loginPage("/login").and().csrf().disable();    }    /**     * 加密方式,恢复以前模式     * @return     */    @Bean    public static NoOpPasswordEncoder PasswordEncoder(){ return (NoOpPasswordEncoder)NoOpPasswordEncoder.getInstance();    }}

spring-security详解

这个地方写的时候出现过一个问题,一直报个错误
Hint: This may be the result of an unspecified view, due to default view name generation
后面百度终于查到,说是没加一个包,加了就可以了
<dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>

RBAC权限架构(多对多关联)

spring-security详解
用户绑定角色,然后角色绑定权限,那么创建用户的时候,只要给用户选择对应的角色就赋予对应的权限,可以这样理解。

springsecurity整合RBAC权限架构

sql表

/*Navicat MySQL Data TransferSource Server  : 127.0.0.1Source Server Version : 50717Source Host    : 127.0.0.1:3306Source Database: mayikt_rbacTarget Server Type    : MYSQLTarget Server Version : 50717File Encoding  : 65001Date: 2021-05-25 04:17:30*/SET FOREIGN_KEY_CHECKS=0;-- ------------------------------ Table structure for sys_permission-- ----------------------------DROP TABLE IF EXISTS `sys_permission`;CREATE TABLE `sys_permission` (  `id` int(10) NOT NULL,  `permName` varchar(50) DEFAULT NULL,  `permTag` varchar(50) DEFAULT NULL,  `url` varchar(255) DEFAULT NULL COMMENT '请求url',  PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ------------------------------ Records of sys_permission-- ----------------------------INSERT INTO `sys_permission` VALUES ('1', '查询用户', 'getUser', '/getUser');INSERT INTO `sys_permission` VALUES ('2', '添加用户', 'addUser', '/addUser');INSERT INTO `sys_permission` VALUES ('3', '修改用户', 'updateUser', '/updateUser');INSERT INTO `sys_permission` VALUES ('4', '删除用户', 'delUser', '/delUser');-- ------------------------------ Table structure for sys_role-- ----------------------------DROP TABLE IF EXISTS `sys_role`;CREATE TABLE `sys_role` (  `id` int(10) NOT NULL,  `roleName` varchar(50) DEFAULT NULL,  `roleDesc` varchar(50) DEFAULT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ------------------------------ Records of sys_role-- ----------------------------INSERT INTO `sys_role` VALUES ('1', 'admin', '管理员');INSERT INTO `sys_role` VALUES ('2', 'add_user', '添加管理员');-- ------------------------------ Table structure for sys_role_permission-- ----------------------------DROP TABLE IF EXISTS `sys_role_permission`;CREATE TABLE `sys_role_permission` (  `role_id` int(10) DEFAULT NULL,  `perm_id` int(10) DEFAULT NULL,  KEY `FK_Reference_3` (`role_id`),  KEY `FK_Reference_4` (`perm_id`),  CONSTRAINT `FK_Reference_3` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`),  CONSTRAINT `FK_Reference_4` FOREIGN KEY (`perm_id`) REFERENCES `sys_permission` (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ------------------------------ 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 ('1', '3');INSERT INTO `sys_role_permission` VALUES ('1', '4');INSERT INTO `sys_role_permission` VALUES ('2', '2');-- ------------------------------ Table structure for sys_user-- ----------------------------DROP TABLE IF EXISTS `sys_user`;CREATE TABLE `sys_user` (  `id` int(10) NOT NULL,  `username` varchar(50) DEFAULT NULL,  `realname` varchar(50) DEFAULT NULL,  `password` varchar(50) DEFAULT NULL,  `createDate` date DEFAULT NULL,  `lastLoginTime` date DEFAULT NULL,  `enabled` int(5) DEFAULT NULL,  `accountNonExpired` int(5) DEFAULT NULL,  `accountNonLocked` int(5) DEFAULT NULL,  `credentialsNonExpired` int(5) DEFAULT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ------------------------------ Records of sys_user-- ----------------------------INSERT INTO `sys_user` VALUES ('1', 'mayikt_admin', '张三', '99743025dc21f56c63d0cb2dd34f06f5', '2018-11-13', '2018-11-13', '1', '1', '1', '1');INSERT INTO `sys_user` VALUES ('2', 'mayikt_add', '小余', 'a5a6996f2e1953161522a93cbb5fb556', '2018-11-13', '2018-11-13', '1', '1', '1', '1');-- ------------------------------ Table structure for sys_user_role-- ----------------------------DROP TABLE IF EXISTS `sys_user_role`;CREATE TABLE `sys_user_role` (  `user_id` int(10) DEFAULT NULL,  `role_id` int(10) DEFAULT NULL,  KEY `FK_Reference_1` (`user_id`),  KEY `FK_Reference_2` (`role_id`),  CONSTRAINT `FK_Reference_1` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`),  CONSTRAINT `FK_Reference_2` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ------------------------------ Records of sys_user_role-- ----------------------------INSERT INTO `sys_user_role` VALUES ('1', '1');INSERT INTO `sys_user_role` VALUES ('2', '2');
然后就是SpringBoot整合mybatis(略)
那么我们现在要做的就是把以前写死的账号、密码、url都要通过查询数据动态的拿到,那么这个就是我们接下来要做的事情。
SecurityConfig
import com.my.mapper.PermissionMapper;import com.my.model.PermissionModel;import com.my.service.serviceimpl.MemberDetailsServiceimpl;import com.my.utils.MD5Util;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;import org.springframework.security.crypto.password.NoOpPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.stereotype.Component;import java.util.List;@Component@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    @Autowired    private PermissionMapper permissionMapper;    @Autowired    private MemberDetailsServiceimpl memberDetailsServiceimpl;    /**     * 新增Security     * 授权账户     * @param auth     * @throws Exception     */    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(memberDetailsServiceimpl).passwordEncoder(new PasswordEncoder() {     /**      * 对用户输入的密码进行加密      * @param charSequence      * @return      */     @Override     public String encode(CharSequence charSequence) {  String encode = MD5Util.encode((String) charSequence);  return encode;     }     /**      * 加密比对      * @param charSequence 输入明文密码      * @param password  数据库中的密码      * @return true 登陆成功  false 密码错误      */     @Override     public boolean matches(CharSequence charSequence, String password) {  //输入的密码进行加密  String rawPass = MD5Util.encode((String) charSequence);  //对比密码  return  password.equals(rawPass);     } });    }    /**     * 认证方式     * @param http     * @throws Exception     */    @Override    protected void configure(HttpSecurity http) throws Exception { ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests = http.authorizeRequests(); //查询到所有的权限 /**  * sql  * SELECT  *     id,  *     permName,  *     permTag,  *     url  * FROM  *     sys_permission  */ List<PermissionModel> permissionList = permissionMapper.getPermissionList(); permissionList.forEach(x->{     //添加规则     authorizeRequests.antMatchers(x.getUrl()).hasAnyAuthority(x.getPermTag()); }); authorizeRequests .antMatchers("/login").permitAll() .antMatchers("/**").fullyAuthenticated().and().formLogin()     //添加自定义跳转页面 .loginPage("/login").and().csrf().disable();    }    /**     * 加密方式,恢复以前模式     * @return     */    @Bean    public static NoOpPasswordEncoder PasswordEncoder(){ return (NoOpPasswordEncoder)NoOpPasswordEncoder.getInstance();    }}
重写UserDetailsService.loadUserByUsername方法
import com.my.mapper.PermissionMapper;import com.my.mapper.UserMapper;import com.my.model.PermissionModel;import com.my.model.UserModel;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.stereotype.Component;import java.util.ArrayList;import java.util.List;/** * 重写UserDetailsService.loadUserByUsername方法 */@Componentpublic class MemberDetailsServiceimpl implements UserDetailsService {    @Autowired    private UserMapper userMapper;    @Autowired    private PermissionMapper permissionMapper;    @Override    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { //根据登陆的userName查询这个用户 /**  * sql  *SELECT  *     id,  *     username,  *     realname,  *     PASSWORD,  *     createDate,  *     lastLoginTime,  *     enabled,  *     accountNonExpired,  *     accountNonLocked,  *     credentialsNonExpired  * FROM  *     sys_user  * where  *     username=#{username}  */ UserModel user = userMapper.getUser(userName); if(user == null){     return null; } //根据用户查询权限 /**  * sql  * SELECT  *s6.id,  *s6.permName,  *s6.permTag,  *s6.url  *   FROM  *( SELECT s2.user_id FROM sys_user s1 LEFT JOIN sys_user_role s2 ON s1.id = s2.user_id WHERE s1.username = #{username} ) s4  *LEFT JOIN sys_role s3 ON s4.user_id = s3.id  *LEFT JOIN sys_role_permission s5 ON s5.role_id = s3.id  *LEFT JOIN sys_permission s6 ON s6.id = s5.perm_id  */ List<PermissionModel> permissionList = permissionMapper.getPermission(user.getUsername()); //创建权限集合 List<GrantedAuthority> grantedAuthorities = new ArrayList<>(); //添加权限 permissionList.forEach(x ->{     grantedAuthorities.add(new SimpleGrantedAuthority(x.getPermTag())); }); //将权限绑定到user user.setAuthorities(grantedAuthorities); return user;    }}
UserModel 这个地方要注意,这个User实体类实现了UserDetails重写了方法,那么这些方法默认是返回false,我们要改成true,不然登陆会出现失败的情况。还有一个问题,就是重写UserDetails的方法,刚好我们字段也有一个类似的字段,在启动的时候项目就检查到这个不符合规范,最后我也是将我实体类中的字段set get方法删除。
/** * 用户表 */public class UserModel implements UserDetails {    private Integer id;    private String username;    private String realname;    private String password;    private Date createDate;    private Date lastLoginTime;    private Integer enabled;    private Integer accountNonExpired;    private Integer accountNonLocked;    private Integer credentialsNonExpired;//用户跟权限板顶,一对多    private List<GrantedAuthority> authorities = new ArrayList<>();    @Override    public List<GrantedAuthority> getAuthorities() { return authorities;    }    public void setAuthorities(List<GrantedAuthority> authorities) { this.authorities = authorities;    }    public Integer getId() { return id;    }    public void setId(Integer id) { this.id = id;    }    public String getUsername() { return username;    }    @Override    public boolean isAccountNonExpired() { return true;    }    @Override    public boolean isAccountNonLocked() { return true;    }    @Override    public boolean isCredentialsNonExpired() { return true;    }    @Override    public boolean isEnabled() { return true;    }    public void setUsername(String username) { this.username = username;    }    public String getRealname() { return realname;    }    public void setRealname(String realname) { this.realname = realname;    }    public String getPassword() { return password;    }    public void setPassword(String password) { this.password = password;    }    public Date getCreateDate() { return createDate;    }    public void setCreateDate(Date createDate) { this.createDate = createDate;    }    public Date getLastLoginTime() { return lastLoginTime;    }    public void setLastLoginTime(Date lastLoginTime) { this.lastLoginTime = lastLoginTime;    }}
MD5加密password
import java.security.MessageDigest;public class MD5Util {    //盐    private static final String SALT = "mayikt";    public static String encode(String password) { password = password + SALT; MessageDigest md5 = null; try {     md5 = MessageDigest.getInstance("MD5"); } catch (Exception e) {     throw new RuntimeException(e); } char[] charArray = password.toCharArray(); byte[] byteArray = new byte[charArray.length]; for (int i = 0; i < charArray.length; i++)     byteArray[i] = (byte) charArray[i]; byte[] md5Bytes = md5.digest(byteArray); StringBuffer hexValue = new StringBuffer(); for (int i = 0; i < md5Bytes.length; i++) {     int val = ((int) md5Bytes[i]) & 0xff;     if (val < 16) {  hexValue.append("0");     }     hexValue.append(Integer.toHexString(val)); } return hexValue.toString();    }}
演示admin账户,全部权限。我只写了一个查询,其他省略了。

spring-security详解
spring-security详解
spring-security详解

演示mykt-add,这个账户只有add权限,那么我们去用这个账户登陆去查询看看

spring-security详解
spring-security详解
spring-security详解

最后我将代码上传到百度网盘

链接:https://pan.baidu.com/s/1ImSsNg1XdMlZEU922jEiKA
提取码:yyds