1.前言
之前项目用的是SSM框架,所以我们选用的安全框架是shiro,但是因为技术主管把我们分散做的模块整合到一起做成微服务的形式,所以我们就用springboot重新将我们的项目迁移了过来.
之后改吧改吧,把大部分的逻辑全部都迁移过来了,之后就是加入安全框架了,因为现在使用的springboot,所以我们选择使用springsecurity这个安全框架,虽然说springboot能够和springsecurity完美配合,但是就算这样,up还是搞了一天才把整个的安全框架嵌了进来.究其原因,还是up自己太菜了.
行了话不多说,这里把完整的教程贡献出来,希望对你有所帮助.
2.流程
其实整个流程还是比较简单的,毕竟都是spring一家的,所以适配起来,的确相对来说比较的简单.
其实主要就分为三个部分,这三个部分分别是 UserDetails UserDetailsService 和SecurityConfig
UserDetails
因为我们最后都是需要从数据库里面读取账户信息的,所以我们需要在我们定义的用户实体类中实现UserDetails接口中的一些方法,这其中主要就是包括用户 角色,权限 那部分的信息.
UserDetailsService
其实大家看到Service就知道是什么意思了,就是需要我们将我们在对应的UserService中实现UserDetailsService的方法,主要就是实现用户的 认证授权 操作
SecurityConfig
这个一看名字就知道是配置文件了.这里主要配置 密码的加密方式,HTTP过滤 等功能
接下来就是详细的教程了
2.1导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
2.2用户实体类实现UserDetails
这里我们点进来看一下,发现UserDetails里面主要是下面这几个属性:
这里我们主要将一下这几个属性的含义
Collection<? extends GrantedAuthority> getAuthorities();//用户的权限集
String getPassword();//用户的加密后的密码
String getUsername();//应用内唯一的用户名
boolean isAccountNonExpired();//账户是否过期
boolean isAccountNonLocked();//账户是否锁定
boolean isCredentialsNonExpired();//凭证是否过期
boolean isEnabled();//用户是否可用
当我们继承了UserDetails这个接口之后我们就需要重写上述的所有方法,同下面的代码:
private Collection<? extends GrantedAuthority> authorities;//这里说是权限集,其实里面存放的其实是用户的角色信息,并且是字符串的形式 @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities;//这里的返回类型是固定的,不能修改成你自己定义的角色对象 } @Override public String getUsername() { return loginName;//这里返回你自己定义的唯一用户名的白能量名称 } //下面四个默认都返回true就行了 @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; }
2.3用户业务层实现UserDetailsService
这里我们需要将我们的UserService继承UserDetailsService,然后去实现他里面的方法,这里我们点进源码看了之后发现,他里面就一个方法
这里我们重写一下他的这个方法
@SneakyThrows @Override public UserDao loadUserByUsername(String username) throws AuthenticationException { //首先通过用户名查询是否有该用户 UserDao userDao=userDaoMapper.getUserByLoginName(username); //System.out.println(userDao); //如果用户存在就开始执行身份认证以及授权的相关操作 if(userDao!=null) { // HttpSession session = request.getSession(); // session.setAttribute("userDao",userDao); // session.setAttribute("sessusername",username); //这里是判断用户的状态即是否锁定 if(userDao.getState()==0) { throw new AccountLockedException("账户已锁定!"); } else { List<GrantedAuthority> authorities = new ArrayList<>(); //查询出来该用户的角色列表 List<RoleDao> roleDaoList=roleService.getRoleByUserId(userDao.getUserId()); //权限列表 List<RightDao> rightDaoList=new ArrayList<>(); for(RoleDao roleDao:roleDaoList) { authorities.add(new SimpleGrantedAuthority("ROLE_"+roleDao.getName())); for(RightDao rightDao:rightService.getRightsByRoleId(roleDao.getRoleId())) { rightDaoList.add(rightDao); } } // System.out.println(username+"用户已经登录"); // System.out.println("有以下角色"); // for(GrantedAuthority a:authorities) // System.out.println(a); // System.out.println("有以下权限"); // for(RightDao rightDao:rightDaoList) // System.out.println(rightDao); return new UserDao(username,userDao.getPassword(),authorities,rightDaoList); } } else { throw new UsernameNotFoundException("用户名不存在!"); } }
这里面有几个注意点
1. 对于用户的角色信息,springsecurity都是将该信息存储在 authorities这个列表之中,并且当我们点进去查看里面存储的数据类型的时候我们可以发现GrantedAuthority本质上其实是一个字符串
之后我们在查看我们添加到 authorities列表中的SimpleGrantedAuthority是什么样子的
我们查看之后可以发现他的构造函数只有一个,并且构造函数的变量就是字符串形式的role,并非是我们自己定义的role对象,并且这里有一个注意点就是我们添加的role字符串必须要是这样的形式: ROLE_角色名 否则springsecurity是识别不了角色信息的.
3. 还有一个就是我们最后的返回对象,他默认的返回对象是UserDetails
所以他默认的返回对象应该是这样的
return new UserDao(username,userDao.getPassword(),authorities);
返回一个包含用户名,用户名真实密码,角色集的User对象,但是我这里额外又添加了一个属性即权限集,是因为我后面可能会用到权限操作,所以我将它一同返回了,这个属性不是必须的.
但是如果你有额外返回的属性,那么你就需要在User对象定义一个你相应的构造函数,就如同我这样:
注意密码的返回形式,我们这里是直接返回的用户的真实密码(这里已经加密过了),否则是无法进行身份验证的环节的.
这样基本的 身份验证以及授权操作 就已经完成了
2.4编写SpringSecurity的配置类SecurityConfig
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled=true,jsr250Enabled=true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserService userService; @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public BCryptPasswordEncoder passwordEncoder() { // 设置默认的加密方式 return new BCryptPasswordEncoder(); } @Autowired public void configure(AuthenticationManagerBuilder builder) throws Exception { builder.userDetailsService(userService).passwordEncoder(passwordEncoder()); } /** * 静态资源设置 */ // @Override // public void configure(WebSecurity webSecurity) { // //不拦截静态资源,所有用户均可访问的资源 // webSecurity.ignoring().antMatchers( // "/", // "/css/**", // "/js/**", // "/images/**", // "/layui/**" // ); // } /** * http请求设置 */ @Override public void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/","/login").permitAll() .antMatchers("/admin/**").hasRole("admin") .anyRequest().authenticated() .and() .formLogin() .usernameParameter("username") .passwordParameter("password") .defaultSuccessUrl("/swagger-ui.html") .failureUrl("/login?error") .permitAll() .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login") .permitAll() .and() .httpBasic() .disable() .csrf() .disable(); } }
这里我们主要注意下面这几个点:
- 我们必须要将 加密规则 注入到spring容器中,否则会报错,其次就是将加密规则添加到我们的service,这样在进行身份验证的时候它才能解析我们已经加密过的密码,否则他是解析不了的,主要就是这段代码:
@Bean public BCryptPasswordEncoder passwordEncoder() { // 设置默认的加密方式 return new BCryptPasswordEncoder(); } @Autowired public void configure(AuthenticationManagerBuilder builder) throws Exception { builder.userDetailsService(userService).passwordEncoder(passwordEncoder()); }
- 定义我们的过滤条件
这里主要是在 configure(HttpSecurity http),看到参数是HTTP,我们就知道主要是对HTTP请求进行过滤,认证等操作,首先我们看第一段代码:
.authorizeRequests() .antMatchers("/","/login").permitAll() .antMatchers("/admin/**").hasRole("admin") .anyRequest().authenticated()
这里我们将登陆页面定义为全部允许,之后定义/admin下的所有请求都需要拥有admin角色的用户才能够访问,之后就是所有的所有的请求都需要用户在已经登录的情况下才能够进行访问
每当一段请求规则定义完成之后,如果你想要的重新定义另一段请求规则可以通过and进行隔断,进行再一次的请求规则编写
这里我们看第二段代码:
.formLogin() .usernameParameter("username") .passwordParameter("password") .defaultSuccessUrl("/swagger-ui.html") .failureUrl("/login?error") .permitAll()
自定义登录页面以及错误页面
第三段代码:
.logout() .logoutUrl("/logout") .logoutSuccessUrl("/login") .permitAll()
这里我们是定义注销操作,注销之后用户信息就失效,任何请求都需要重新登录之后才能完成
3.效果演示
我们先来看看admin用户的一些操作:
我们可以看到我们访问swagger的时候是被直接拦截下来的,只有在登录之后才能访问,并且因为是admin用户,所以所有的操作都是可以执行的,之后在我们注销之后我们再次访问swagger的时候可以发现请求依旧被拦截下来了,所以注销功能也是实现了
我们再来看看 游客 的操作:
因为是游客,所以登录进来之后,这些操作都是不被允许的,可以看到都是报403错误,意思就是没有权限访问.