1.背景
Spring Security是一套成熟的安全框架,充分利用了依赖注入和AOP的实现安全功能。安全框架包含两大部分,一个是认证,一个是授权。
2.原理
其实Spring Security的功能实现主要是依靠DelegatingFilterProxy这一个多个过滤器来实现的。我们需要把这个过滤器注册到web容器上。
3.内容
1.如何配置自己需要的信息?
肯定是需要一个配置类,例如一个配置类继承WebSecurityConfigurationAdapter,重写里面的方法即可。
@Configuration
@EnableWebSecurity
public class ConfigurationSecurity extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}
@Override
protected void configure(HttpSecurity http) throws Exception {
}
}
2.用户认证
(1)内存中保存用户信息
切记authorities与roles的区别,虽然两个方法都是授予权限的方法,但是roles会自动加上‘ ROLE_ ’的前缀,而authorities不会。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("llg").password("llg").authorities("user").authorities("admin")
.and()
.withUser("llx").password("llx").roles("admin").roles("user");
}
(2)jdbc中获取用户
可以从数据库通过jdbc的方式拿出用户名和密码,可以自已自定义查询语句,注意这个字段true,代表的是是否开启本用户,在下方的用户自定义user的时候可以看到重写了isEnabled()的方法,返回的true与这里的true一个意思。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username,password,true from user where username=?")
.authoritiesByUsernameQuery("select username,ROLE_USER from user where username=?");
}
(3)通用的用户实现方式(最适用)
获取数据方式从jdbc和内存这种局限性扩展到jpa和非关系型数据库
主要方法就是实现UserDetailService接口,上面的jdbc和内存实际上就是实现了这个接口。
(1)创建一个bean实现此接口,不需要自定义User对象,直接传入参数然后返回spring security 的默认user对象即可。然后配置到配置类里面
public class UserDetailService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user=userDao.findByUsername(s);
List<GrantedAuthority> authorityList=new ArrayList<>();
authorityList.add(new SimpleGrantedAuthority(user.getROLE_USER()));
return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPassword(),authorityList);
}
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService());
}
(2)把需要返回的user对象通过实现UserDetails来实现自定义user对象返回,之后步骤与上述相同,istrue属性可以删去。自定义对象需要自己实现返回授权,如果一个用户有多个权限,需要将所有权限添加到数组然后返回,另外4个方法也需要把值全部改成true.
@Entity
@Table(name = "user")
public class User implements UserDetails {
@Id
public String id;
public String username;
public String password;
public String phonenumber;
public String address;
public String email;
@Column(name="ROLE_USER")
public String ROLE_USER;
public int istrue;
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> list=new ArrayList<>();
list.add(new SimpleGrantedAuthority(this.getROLE_USER()));
return list;
}
public class UserDetailService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user=userDao.findByUsername(s);
return user;
}
}
3.用户授权
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//.csrf().disable()//关闭跨域请求
.formLogin()//表单登录
.usernameParameter("sd")//设置输入款用户的name属性
.loginPage("/login")//设置登录页面,与过滤器有关
//.failureForwardUrl("")//登录失败跳转页面
.and()
//.cors()//加入可跨域的ip
.rememberMe()//开启cookie存储用户信息
.tokenValiditySeconds(1209600)//cookie的存储有效期2周
.key("asdgfdgdfgsd45edsadsa346lqrere")//cookie的私钥
.and()
.logout()
.logoutSuccessUrl("/")//退出成功跳转页面
.logoutUrl("/logout")//指定注销的页面,与过滤器有关
.and()
.authorizeRequests()//开始请求权限配置
.antMatchers("/llg/**").hasAnyAuthority("user","admin")
.antMatchers("/admin/**").hasAuthority("admin")
.antMatchers("/admin/**").hasIpAddress("")//填写可以访问的ip
.anyRequest().permitAll();//其余所有请求都可以访问
//.anyRequest().permitAll();//其余所有请求都不可以访问
}
4. 获取授权用户信息
首先通过key从session中获取security上下文对象,由此也可以看出security基于session存储用户信息。
(1)SecurityContextImpl就是上下文对象了,有数据库查询的用户信息principal,前台传过来的用户输入的密码credentails。有用户权限authorities,用户的sessionid(deatail)
(2)@AuthernticationPrincipal User user 则是我们的用户对象实体类,继承了UserDeatils接口,可以拿到整个从数据库获取到的用户信息。
(3)princaipal 是用户的接口对象,有获取用户名的方法。
public String getUsername(HttpServletRequest request,Principal principal,@AuthenticationPrincipal User user) {
String username = "";
try {
SecurityContextImpl securityContextImpl = (SecurityContextImpl) request.getSession().getAttribute("SPRING_SECURITY_CONTEXT");
} catch (Exception ex) {
username = "";
}
return username;
}
5.使用spring security结合md5加密
首先需要一个mdb加密工具类
public class MD5Util {
private static final String SALT = "tamboo";
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();
}
}
然后配置这个工具类到spring security的配置类上
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.eraseCredentials(true).userDetailsService(customUserService()).passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence rawPassword) {
return MD5Util.encode((String)rawPassword);
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(MD5Util.encode((String)rawPassword));
}
});
}
还要记住在存储用户信息的时候记得将前台页面传过来的密码加密后存储到数据库。
大概流程就是:使用MD5加密后放到数据库,前台登录时候传入明文密码,spring security用户认证的时候将明文密码加密后再跟数据库的加密密码匹配即可。
4.总结
spring security 是个非常出色的框架,集成了我们可以想到的所有功能接口,我们可以相对应的去实现,当然了spring security是默认以session存储信息的,接下来我将通过spring security 与jwt的结合去继续深入spring security。