赞
踩
想要项目源码可以联系我,有问题看到就会回复,互相学习
security登录功能,通过登录的用户名(username)来判断输入的用户名密码是否正确
security调用loadUserByUsername这个方法,通过用户名来加载用户信息,如果加载到了改用户名的用户信息,则登录成功
否则登录失败
而这个方法是UserDetailsService接口的唯一方法,所以我们只需要重写这个方法来帮助security完成对用户信息的加载
这个方法的返回值是UserDetails类型的对象,这个对象里面包含用户的用户名,密码,权限信息,
所以我们通过用户的实体类来实现UserDetails接口从而使security调用loadUserByUsername方法来获取UserDetails类型的返回值并完成登录校验工作,该接口还有isAccountNonExpired,isAccountNonLocked等方法,
这些方法的返回值都需要修改为true
security对核心接口UserDetails,UserDetailsService和核心方法loadUserByUsername的具体调用在源码中
已经封装好了,那我们如何配合security的源码来完成对这些核心接口和方法的调用呢,毫无疑问需要写一个security的配置类
WebSecurityConfig,该类继承WebSecurityConfigurerAdapter,并需要在该类上加上注解@EnableWebSecurity,
如果后面需要通过权限控制接口是否能被调用,我们还可以先加上注解@EnableGlobalMethodSecurity并传入以下参数
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true,jsr250Enabled = true)
WebSecurityConfig继承WebSecurityConfigurerAdapter后需要重写1个核心方法
void configure(HttpSecurity http) throws Exception ;
configure这个方法是用来可以用来配置登录,登出,是否需要权限控制等配置
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/books","/main").authenticated() .antMatchers("/**").permitAll() .anyRequest().permitAll() .and() .exceptionHandling() .accessDeniedHandler(new CustomAccessDeniedHandler()) // .authenticationEntryPoint() .and() .formLogin() .loginPage("/login") .loginProcessingUrl("/clickLogin") .successHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { response.setContentType("application/json;charset=UTF-8"); PrintWriter pw = response.getWriter(); Res result = new Res(true, "登录成功"); String s = JSON.toJSONString(result); pw.write(s); pw.close(); } }) .failureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { response.setContentType("application/json;charset=UTF-8"); PrintWriter pw = response.getWriter(); Res result = new Res(false, "账号密码错误"); String s = JSON.toJSONString(result); pw.write(s); pw.close(); } }) .permitAll() .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login") .and() //允许页面镶嵌 .headers() .frameOptions().sameOrigin() .httpStrictTransportSecurity().disable() .and() .csrf() .disable() .headers().frameOptions().disable(); }
我们还需要注入一个方法
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
这段代码表示security通过userDetails来校验用户登录,当然也有通过内存信息来校验登录的
auth
.inMemoryAuthentication().withUser("user").password("123456").roles("ADMIN");
除此之外,我们还要特别注意security验证密码的时候是必须加密,security验证密码的过程是这样的:
将用户输入的密码进行加密,加密后与从userDetails详细用户信息中获取的用户密码进行核对,所以userDetails中的密码必须是加密过的,也就是说数据库中的密码我们需要加密,一般在注册的时候先对密码加密再存入数据库
我们可以自定义选择加密方式,我们一般使用的是BCryptPasswordEncoder
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
然后在这个地方配置加密规则()
auth
.userDetailsService(userService).passwordEncoder(passwordEncoder());
我们在登录之后,一般都会获取该用户的相关信息,包括权限信息,所以我们在实体类中需要一个有关权限的属性,并且是集合类型,因为一个用户可能有多个权限,在实体类中我们可以加入以下属性与方法
private Collection<SimpleGrantedAuthority> authorities;
@Override
public Collection<SimpleGrantedAuthority> getAuthorities() {
return authorities;
}
在mapper.xml中,我们通过联表查询用户的所有信息,特别要注意的是我们必须要获取用户的用户名与密码,
因为前面所讲的登录认证需要用到,然后我们配置权限这个属性的时候,注意在collection标签中加上notNullColumn这个属性,
因为如果某些用户的权限为空的话,security可能会缓存上一个用户的权限信息
<select id="getUserAndRoles" resultMap="Role" parameterType="String" > SELECT u.*,r.role_name from tbl_user u left join tbl_user_role ur on u.id = ur.user_id left join tbl_role r on ur.role_id=r.id where u.username=#{username} </select> <resultMap id="Role" type="com.tty.bean.User"> <id column="id" property="id"/> <result column="username" property="username"/> <result column="password" property="password"/> <result column="name" property="name"/> <collection property="authorities" ofType="org.springframework.security.core.authority.SimpleGrantedAuthority" notNullColumn="role_name"> <result column="role_name" property="role"/> </collection> </resultMap>
并且我们要保证security获取的权限名字是以ROLE_开头,我们可以在数据库中添加权限的时候直接加上ROLE_,或者我们可以自定义一个
GrantedAuthority的实现类(这个实现类可以模仿SimpleGrantedAuthority这个类来写,如以下自定义实现类的代码,这样就可以让security给我们的权限自动加上ROLE_
@Data public class CustomSimpleAuthority implements GrantedAuthority { private static final long serialVersionUID = 530L; private int id; private String authority; private String name; public CustomSimpleAuthority(String authority) { Assert.hasText(authority, "A granted authority textual representation is required"); this.authority = authority; } @Override public String getAuthority() { return "ROLE_"+this.authority; } @Override public String toString() { return name; } }
,然后将UserDetails中的authorities类型的泛型改为该实现类
例如:
private Collection<CustomSimpleAuthority> authorities;
配置 好权限之后,我们可以在某些接口的方法上使用相关注解,例如@RolesAllowed,从而使得该接口有特定权限才能访问,
如以下代码,表示admin页面只有具有ROLE_ADMIN权限的用户才可以访问
@RolesAllowed({"ROLE_ADMIN"})
@RequestMapping("/admin")
public String admin(){
return "admin";
}
权限控制我们也需要在自定义的security配置类WebSecurityConfig中配置有关异常处理的信息,如下
http
.exceptionHandling()
.accessDeniedHandler(new SimpleAccessDeniedHandler())//权限异常处理
.authenticationEntryPoint(new SimpleAuthenticationEntryPoint())//认证异常处理
这些配置好后,如果我们使用没有ROLE_ADMIN权限的用户访问这个admin页面
,会出现AccessDeniedException异常并且依旧可以访问该admin页面,因为我们没有进行全局异常捕获异,我们可以利用SpringMVC的全局异常捕获,l如下代码:
//使用该注解让springmvc 统一捕捉异常, 并且作出处理
@RestControllerAdvice
public class BookExceptionAdvice {
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(value = AccessDeniedException.class)
public Res AccessDeniedException(AccessDeniedException e){
e.printStackTrace();
return new Res(false,e.getMessage());
}
}
WebSecurityConfig中配置后,再通过全局异常捕获以后,我们基本可以实现权限控制
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。