设置账号和密码
第一种方法:配置文件(不常用)
application
spring.security.user.name=admin
spring.security.user.password=123
第二种方法:配置类
需要继承WebSecurityConfigurerAdapter
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//密码加密
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String encode = bCryptPasswordEncoder.encode("456");
//登录认证
auth.inMemoryAuthentication()
.withUser("user")
.password(encode)
.roles("user");
}
@Bean
public PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
整体逻辑:在security登录中,认证只有两个步骤事需要编程人员来做的:
1、获取用户信息
2、返回用户信息以及权限。
剩下的认证、判断等,全部由security内部完成(没怎么看源码,所以具体怎么完成的不太清楚)
实现:
第一,我需要一个用来给security内部返回用户信息的类,config
第二,我需要一个获取用户信息的类,service
config
@Configuration
@EnableWebSecurity
public class ConfigTest extends WebSecurityConfigurerAdapter{
@Autowired
private MyService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
public PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
这里继承WebSecurityConfigurerAdapter类,重写configure方法,这是一个空方法,这里的具体要实现什么功能主要看括号中的AuthenticationManagerBuilder,这是一个身份验证管理器,既然是身份验证管理器,我就需要给它一个身份,所以就引出了userDetailsService用户详细信息,现在转到MyService类。
@Service
public class MyService implements UserDetailsService{
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException{
QueryWrapper<com.tan.pojo.User> wrapper = new QueryWrapper<>();
wrapper.eq("username",s);
com.tan.pojo.User user = userMapper.selectOne(wrapper);
if(user==null){
throw new UsernameNotFoundException("用户名不存在");
}
return new User(user.getUsername(),new BCryptPaswordEncoder().encode(user.getPassword()),auths); //模拟加密,return加密后的密码
这里主要用于获取用户,可以看到loadUserByUsername方法中的前三行,使用了mybatisPlus来查找数据库中用户的相关信息赋给user。接下来就是判断是该用户是否存在,不存在就直接报出异常。存在就把当前用户通过上面ConfigTest类给放到AuthenticationManagerBuilder中,剩下的对比操作就由security来完成。
设置认证
根据学习需要来设置哪些网址可以不用登录就能访问,哪些需要登录才能访问。
重写源码中的http安全方法
protected void configure(HttpSecurity http) throws Exception {
this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http.authorizeRequests((requests) -> {
((AuthorizedUrl)requests.anyRequest()).authenticated();
});
http.formLogin();
http.httpBasic();
}
根据当前学习需要,重写后的代码
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() //启用自定义登录页面
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login").permitAll() //登录访问路径
.defaultSuccessUrl("/test/index") //默认成功路径, 成功后跳转的地方.
.and().csrf().disable(); //关闭csrf功能, security3默认关闭,security4默认开启
http.authorizeRequests()
.antMatchers("/test/index").permitAll()//给一些网站开绿灯,不需要认证也可以登录
.anyRequest().authenticated(); //所有网站都要需要认证
http.formLogin();
http.httpBasic();
}
这里遇到了一个问题,我重写了此方法之后,原方法中的anyRequest().authenticated();就失效了,所以在设置认证登录的时候,要把这一句话给加上,而且要加在认证的最后面,不然会报错
Error creating bean with name 'springSecurityFilterChain' defined in class path resource
[org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]:
Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException:
Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception;
nested exception is java.lang.IllegalStateException: Can't configure antMatchers after anyRequest
可以看到最后一句,嵌套异常为无法在anyRequest之后配置antMatchers。
还有一点自己认为的疑问。。为什么不能把所有网站都需要认证这个条件默认开启,然后写一个关闭的方法,感觉这样更清晰一点。。
在WebSecurityConfigurerAdapter类中还有一些HttpSecurity的其他方法:
private void applyDefaultConfiguration(HttpSecurity http) throws Exception {
http.csrf(); //csrf功能,默认开启
http.addFilter(new WebAsyncManagerIntegrationFilter()); //过滤器
http.exceptionHandling(); //异常处理
http.headers(); //安全头
http.sessionManagement(); //会话管理器
http.securityContext(); //存储当前用户账号信息和相关权限
http.requestCache(); //请求缓存
http.anonymous(); //匿名
http.servletApi(); //小服务程序API
http.apply(new DefaultLoginPageConfigurer()); //默认登录页面配置器
http.logout(); //退出
}
三、设置授权
授权的方式分为以下四种:
1、hasAuthority
实现:
设置访问权限:
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
设置用户权限:
http.authorizeRequests()
.antMatchers("/test/hello").hasAnyAuthority("admin")
缺点:只能对一个用户添加一个权限。
2、hasAnyAuthority
hasAuthority的升级版,解决了hasAuthority的缺点。
实现:
设置访问权限:
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
设置用户权限:
http.authorizeRequests()
.antMatchers("/test/hello").hasAnyAuthority("admin","user")
3、hasRole
与hasAuthority不同的是需要在设置访问权限的时候添加一个前缀:ROLE_**
实现:
设置访问权限:
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_vvv");
设置用户权限:
http.authorizeRequests()
.antMatchers("/test/hello").hasRole("vvv")
这里需要注意一下,当使用hasRole时,不能在用户权限中设置ROLE_ 为前缀的权限,会报嵌套异常。
4、hasAnyRole
相当于是hasRole和hasAnyAuthority的结合,一个用户可以有多个权限,但是设置访问权限的时候需要加前缀ROLE_**
实现:
设置访问权限:
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin");
设置用户权限:
http.authorizeRequests()
.antMatchers("/test/hello").hasAnyRole("admin","aaa")