随便写一下,增删改查返回对应的字符
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
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("mykt").password("mykt").authorities("/");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic();
}
@Bean
public static NoOpPasswordEncoder PasswordEncoder(){
return (NoOpPasswordEncoder)NoOpPasswordEncoder.getInstance();
}
}
输入对应的账号密码就可以访问对应的接口,这个非常简单。
From表单验证
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().formLogin();
}
效果图(账号密码没变,页面自带,不需要自己写)
配置权限规则(不同的账号对应不同的权限)
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
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@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");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/add").hasAnyAuthority("add")
.antMatchers("/get").hasAnyAuthority("get")
.antMatchers("/update").hasAnyAuthority("update")
.antMatchers("/del").hasAnyAuthority("del")
.antMatchers("/**").fullyAuthenticated().and().formLogin();
}
@Bean
public static NoOpPasswordEncoder PasswordEncoder(){
return (NoOpPasswordEncoder)NoOpPasswordEncoder.getInstance();
}
}
创建了五个账号,mykt-admin、mykt-add 、mykt-update 、mykt-get 、mykt-del,那么从账号名称中就可以看出来这些账号对应的权限
mykt-add测试
mykt-admin测试
权限不足跳转页面
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;
@Configuration
public 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;
@RestController
public class ErrorController {
@RequestMapping("/error/403")
public String error403(){
return "你当前访问的接口权限不足";
}
@RequestMapping("/error/404")
public String error404(){
return "资源不可用";
}
}
为了演示效果, 正常的环境应该是返回码值,由前端对码值进行错误逻辑判断跳转到对应错误页面
自定义登陆页面
创建页面将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;
@Controller
public 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
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@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");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/add").hasAnyAuthority("add")
.antMatchers("/get").hasAnyAuthority("get")
.antMatchers("/update").hasAnyAuthority("update")
.antMatchers("/del").hasAnyAuthority("del")
.antMatchers("/login").permitAll()
.antMatchers("/**").fullyAuthenticated().and().formLogin()
.loginPage("/login").and().csrf().disable();
}
@Bean
public static NoOpPasswordEncoder PasswordEncoder(){
return (NoOpPasswordEncoder)NoOpPasswordEncoder.getInstance();
}
}
这个地方写的时候出现过一个问题,一直报个错误
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权限架构(多对多关联)
用户绑定角色,然后角色绑定权限,那么创建用户的时候,只要给用户选择对应的角色就赋予对应的权限,可以这样理解。
springsecurity整合RBAC权限架构
sql表
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
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PermissionMapper permissionMapper;
@Autowired
private MemberDetailsServiceimpl memberDetailsServiceimpl;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(memberDetailsServiceimpl).passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
String encode = MD5Util.encode((String) charSequence);
return encode;
}
@Override
public boolean matches(CharSequence charSequence, String password) {
String rawPass = MD5Util.encode((String) charSequence);
return password.equals(rawPass);
}
});
}
@Override
protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests = http.authorizeRequests();
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();
}
@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;
@Component
public class MemberDetailsServiceimpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Autowired
private PermissionMapper permissionMapper;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
UserModel user = userMapper.getUser(userName);
if(user == null){
return null;
}
List<PermissionModel> permissionList = permissionMapper.getPermission(user.getUsername());
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
permissionList.forEach(x ->{
grantedAuthorities.add(new SimpleGrantedAuthority(x.getPermTag()));
});
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账户,全部权限。我只写了一个查询,其他省略了。
演示mykt-add,这个账户只有add权限,那么我们去用这个账户登陆去查询看看
最后我将代码上传到百度网盘
链接:https://pan.baidu.com/s/1ImSsNg1XdMlZEU922jEiKA 提取码:yyds
|