SpringSecurity Oauth2(前后端不分离)
简介
Spring Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富。
一般来说中大型的项目都是使用SpringSecurity 来做安全框架。小项目有Shiro的比较多,因为相比与SpringSecurity,Shiro的上手更加的简单。
一般Web应用的需要进行认证和授权。
认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户
授权:经过认证后判断当前用户是否有权限进行某个操作
而认证和授权也是SpringSecurity作为安全框架的核心功能。
认证模式
实现Basic认证
Basic认证是一种较为简单的HTTP认证方式,客户端通过明文(Base64编码格式)户名和密码到服务器进行认证,通过常需要配合HTTPS来保证传输的安全
@***ponent
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/*
/新增Security账户 授权的账户
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//需要经过wzx
auth.inMemoryAuthentication().withUser("wzx").password("456").authorities("/");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置认证方式 1.token 2.form表单
http.authorizeHttpRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic();
}
}
form 表单模式
From 表单模式 适合于传统模式项目 前端和后端都是我们Java开发人员自己实现。Vue+SpringBoot
@***ponent
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/*
/新增Security账户 授权的账户
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//需要经过wzx
auth.inMemoryAuthentication().withUser("admin").password("admin").authorities("/");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置认证方式 1.token 2.form表单 设置为BasicHttp认证
http.authorizeHttpRequests().antMatchers("/**").authenticated().and().formLogin();
}
@Bean
public static NoOpPasswordEncoder passwordEncoder(){
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
}
配置权限策略
在企业管理系统平台中,会拆分成n多个不同的账号,每个账号对应不同的接口访问权限,
比如
账号admin所有接口都可以访问;
其他账号只能根据自己的权限进行访问;
//静态配置
@***ponent
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/*
/新增Security账户 授权的账户
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//需要经过wzx
auth.inMemoryAuthentication().withUser("admin").password("admin").authorities("add");
auth.inMemoryAuthentication().withUser("wzx").password("wzx").authorities("update");
//对当前账户进行授权
}
//拦截
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置认证方式 1.token 2.form表单
//http.authorizeHttpRequests().antMatchers("/**").authenticated().and().formLogin();
//.antMatchers("add")方法中设置接口名称 .hasAnyAuthority("add")方法中防止接口所对应的权限
http.authorizeHttpRequests()
.antMatchers("/user/add").hasAnyAuthority("add")
.antMatchers("/user/delete").hasAnyAuthority("delete")
.antMatchers("/user/show").hasAnyAuthority("show")
.antMatchers("/user/update").hasAnyAuthority("update")
.antMatchers("/**").authenticated().and().formLogin();
}
@Bean
public static NoOpPasswordEncoder passwordEncoder(){
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
权限不足页面
统一返回异常类
/*
* 统一返回错误异常类
* */
@RestController
public class ErrorController {
@RequestMapping("/error/403")
public String error(){
return "权限不足";
}
}
@Configuration
public class WebServerAutoConfig {
@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
ErrorPage errorPage400 = new ErrorPage(HttpStatus.BAD_REQUEST, "/error/400");
ErrorPage errorPage401 = new ErrorPage(HttpStatus.UNAUTHORIZED, "/error/401");
ErrorPage errorPage403 = new ErrorPage(HttpStatus.FORBIDDEN, "/error/403");
ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error/404");
ErrorPage errorPage415 = new ErrorPage(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "/error/415");
ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500");
factory.addErrorPages(errorPage400, errorPage401, errorPage403, errorPage404, errorPage415, errorPage500);
return factory;
}
}
自定义登录界面
//拦截
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置认证方式 1.token 2.form表单
//http.authorizeHttpRequests().antMatchers("/**").authenticated().and().formLogin();
//.antMatchers("add")方法中设置接口名称 .hasAnyAuthority("add")方法中防止接口所对应的权限
http.authorizeHttpRequests()
.antMatchers("/user/add").hasAnyAuthority("add")
.antMatchers("/user/delete").hasAnyAuthority("delete")
.antMatchers("/user/show").hasAnyAuthority("show")
.antMatchers("/user/update").hasAnyAuthority("update")
//可以允许login 登录界面不被拦截
.antMatchers("/login.html").permitAll()
//设置自定义登录页面
.antMatchers("/**").authenticated().and().formLogin()
.loginPage("/login.html") //自定义登录界面
.loginProcessingUrl("/login") // 登录接口,与form表单提交链接对应
// .defaultSu***essUrl("/index.html") //登录成功跳转的界面
.and().csrf().disable();
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<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="login">
</form>
</body>
</html>
动态整合RABC权限架构设计
它就是用db查询直接去数据库区查询所具备的所有权限规则和对用户进行授权
1.实现动态拦截
上面是所谓的静态权限拦截,那么动态拦截就是用db查询去数据库查询所有的权限和规则
//拦截
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置认证方式 1.token 2.form表单
//动态拦截
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry
authorizeHttpRequests = http.authorizeRequests();
//查询数据库中所有的权限
List<Perm> permList = permMapper.queryAllPerm();
permList.forEach( p -> {
//动态将接口和所对应的规则匹配
authorizeHttpRequests.antMatchers(p.permUrl).hasAnyAuthority(p.permDesc);
});
authorizeHttpRequests
//可以允许login 登录界面不被拦截
.antMatchers("/login.html").permitAll()
.antMatchers("/test.do").permitAll()
//设置自定义登录页面
.antMatchers("/**").authenticated().and().formLogin()
.loginPage("/login.html") //自定义登录界面
.loginProcessingUrl("/login") // 登录接口,与form表单提交链接对应
.defaultSu***essUrl("/index.html") //登录成功跳转的界面
.and().csrf().disable();
}
2.实现动态授权
首先实现动态授权要穿件一个MemberDetilsService实现UserDetilsService接口,其中loadUserByUsername() 方法是登录后加载该用户所具备的权限,用户实体类也必须要实现UserDetils 接口。
@Service
public class MembersDetailsService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Autowired
PermMapper permMapper;
//登录加载user
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.在登录的时候 调用该方法 userName查询账户是否存在 在验证账户的密码
User user = userMapper.queryByUserName(username);
if (user == null) {
return null;
}
//2.再根据账户的userID 关联查询 角色对应的权限 动态进行添加授权
List<Perm> userPermList = permMapper.queryByUserName(username);
//设置权限
ArrayList<GrantedAuthority> grantedAuthorities = new ArrayList<>();
userPermList.forEach( item -> {
grantedAuthorities.add(new SimpleGrantedAuthority(item.permDesc));
});
user.setAuthorities(grantedAuthorities);
return user;
}
}
用户实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
//必须要实现UserDetails接口
public class User implements UserDetails {
private Integer id; //用户id
private String username; //用户账号
private String password; //用户密码
private List<Role> role;
//用户所有的权限
private List<GrantedAuthority> authorities = new ArrayList<>();
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public boolean isA***ountNonExpired() {
return true;
}
@Override
public boolean isA***ountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
然后去配置动态授权,其中要注意用加密工具对密码实现加解密
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//对当前账户进行授权
auth.userDetailsService(membersDetailsService).passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence rawPassword) {
//使用Md5加密算法
return MD5Util.convertMD5((String) rawPassword);
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
//使用MD5加密后 与数据库中的密码MD5 后验证
String rawPass = MD5Util.convertMD5((String) rawPassword);
boolean result = rawPass.equals(MD5Util.convertMD5(encodedPassword));
return result;
}
});
}
MD5加密工具
public class MD5Util {
/**
* 使用 MD5算法加密生成32位md5码
* @param str
* @return
*/
public static String string2MD5(String str) {
byte[] secretBytes = null;
try {
secretBytes = MessageDigest.getInstance("md5").digest(
str.getBytes());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("没有md5这个算法!");
}
String md5code = new BigInteger(1, secretBytes).toString(16);// 16进制数字
// 如果生成数字未满32位,需要前面补0
for (int i = 0; i < 32 - md5code.length(); i++) {
md5code = "0" + md5code;
}
return md5code;
}
/**
* 可逆的加密解密算法,执行一次加密,两次解密
* @param str
* @return
*/
public static String convertMD5(String str){
char[] a = str.toCharArray();
for (int i = 0; i < a.length; i++){
a[i] = (char) (a[i] ^ 't');
}
String s = new String(a);
return s;
}
}
整合oauth2
什么是oauth2?
OAuth2.0是一个授权协议,他允许软件应用代表(而不是充当)资源拥有者去访问资源拥有者的资源。应用向资源拥有者请求授权,然后去的令牌(Token),并用他来访问资源,并且资源拥有者不用向应用提供用户名和密码等敏感数据。
OAUTH角色划分
-
Resource Server:被授权访问的资源
-
Authotization Server: OAUTH2认证授权中心
-
Recource Owner:用户
-
Client: 使用API的合作伙伴
Oauth应用场景
-
第三方联合登录 如QQ、微信
-
开放接口 蚂蚁金服、腾讯开放接口