spring-security实战

项目架构:spring-boot+spring-security+thymeleaf

1.导包(bootthymeleaf的包请自行导入,版本号我抽的properties)

 

<dependency>
         <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
</dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
 </dependency>

 

2.核心配置类(完整代码最后放出)

2.1自定义security的配置类,然后继承WebSecurityConfigurerAdapter这个抽象类。

2.2打上@EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true)这两个注解

2.3重写configure(HttpSecurity http)方法,使用链式编程使用http.formLogin()方法设置你需要的配置(拦截参数,登录路径,跨域请求等)

2.4重写configure(WebSecurity web)方法,释放静态资源

 

web.ignoring().antMatchers("/**/*.js", "/**/*.css", "/**/*.png", "/**/*.jpg", "/**/*.gif", "/**/*.map");

 

2.5重写configure(AuthenticationManagerBuilder auth)方法,自定义你的登录验证

loginAuthenticationProvider:这是自定义的登录验证类,注入调用
customUserDetailsService:这是自定义的账号存储类,注入调用
customPasswordEncoder:这是自定义的密码加密类,注入调用
loginAuthenticationProvider.setUserDetailsService(customUserDetailsService);
loginAuthenticationProvider.setPasswordEncoder(customPasswordEncoder);
auth.authenticationProvider(loginAuthenticationProvider);
super.configure(auth);

3.security国际化问题

在使用security过程中,因为原生代码中DaoAuthenticationProvider这个类的additionalAuthenticationChecks方法

会在你前端页面登陆失败的时候返回Bad credentials这个消息。这会让系统看上去不那么友好。

解决方法是:

1.在resources文件夹中添加messages_zh_CN.properties这个文件。自定义你想返回前端的话。

2.自定义登录验证类loginAuthenticationProvider(最后放出完整代码)

3.在自定义security的配置类中做相应配置

完整代码:

1.自定义security配置类

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final CustomPasswordEncoder customPasswordEncoder;
    private final CustomUserDetailsService customUserDetailsService;
    private final CommonProperties commonProperties;

    @Autowired
    public SecurityConfiguration(CustomPasswordEncoder customPasswordEncoder, CustomUserDetailsService customUserDetailsService, CommonProperties commonProperties) {
        this.customPasswordEncoder = customPasswordEncoder;
        this.customUserDetailsService = customUserDetailsService;
        this.commonProperties = commonProperties;
    }

    @Autowired
    private LoginAuthenticationProvider loginAuthenticationProvider;

    /**
     * http配置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/doLogin")
                // 指定登录页面及成功后跳转页面
                .successForwardUrl("/loadIndex")
                .and().authorizeRequests()
                .antMatchers(commonProperties.getAllowList())
                // 上述url所有用户都可访问(无需登录)
                .permitAll()
                .and()
                .authorizeRequests()
                .anyRequest()
                // 除上面之外的都需要登录
                .authenticated()
                .and()
                .logout()
                // 指定登出url
                .logoutUrl("/doLogout")
                .invalidateHttpSession(true)
                .and()
                .headers()
                .frameOptions()
                // frame框架
                .sameOrigin()
                // csrf跨域保护忽略特殊url
                .and().csrf().ignoringAntMatchers(commonProperties.getAllowList());
    }

    /**
     * 静态资源放行
     * @param web
     */
    @Override
    public void configure(WebSecurity web) {
        //静态资源过滤
        web.ignoring().antMatchers("/**/*.js", "/**/*.css", "/**/*.png", "/**/*.jpg", "/**/*.gif", "/**/*.map");
    }

    /**
     * 自定义认证方法
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        loginAuthenticationProvider.setUserDetailsService(customUserDetailsService);
        loginAuthenticationProvider.setPasswordEncoder(customPasswordEncoder);
        auth.authenticationProvider(loginAuthenticationProvider);
        super.configure(auth);
    }

    /**
     * 自定义认证管理类
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 自定义密码加密类
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

}

2.自定义账号存储类

@Service
public class CustomUserDetailsService implements UserDetailsService {
    private final AuthTokenService authTokenService;
    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired
    public CustomUserDetailsService(AuthTokenService authTokenService) {
        this.authTokenService = authTokenService;
    }

    /**
     * 将用户详细信息放入security session中
     * @param username
     * @return
     */
    @Override
    public UserDetails loadUserByUsername(String username) {
        try{
            Assert.isTrue(StringUtils.isNotBlank(username),"用户或密码错误,请重新输入");
            // 通过用户名获取用户详细信息,构造权限
            UserDTO userInfo = authTokenService.getUserDetail(username);
            ResourceBO resource = userInfo.getResource();
            List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
            if(resource != null){
                buildAuth(resource, authorities);
            }
            return new UserBO(userInfo, authorities);
        }catch (IllegalArgumentException e){
            throw new UsernameNotFoundException("用户或密码错误,请重新输入");
        }catch (Exception e){
            log.error("获取用户信息异常",e);
            throw new UsernameNotFoundException("用户或密码错误,请重新输入");
        }
    }
}

3.自定义密码加密类

@Service
public class CustomPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {
        return s.equals(encode(charSequence));
    }
}

4.自定义security认证

@Component
public class LoginAuthenticationProvider extends DaoAuthenticationProvider {

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Autowired
    private CustomPasswordEncoder customPasswordEncoder;

    @Autowired
    private void setJdbcUserDetailsService() {
        setUserDetailsService(customUserDetailsService);
    }

    /**
     * 自定义security国际化
     */
    @PostConstruct
    public void initProvider() {
        ReloadableResourceBundleMessageSource localMessageSource = new ReloadableResourceBundleMessageSource();
        localMessageSource.setBasenames("messages_zh_CN");
        messages = new MessageSourceAccessor(localMessageSource);
    }

    /**
     * 复写认证失败方法
     * @param userDetails
     * @param authentication
     * @throws AuthenticationException
     */
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            logger.debug("Authentication failed: no credentials provided");
            throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
        String presentedPassword = authentication.getCredentials().toString();
        if (!customPasswordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            logger.debug("Authentication failed: password does not match stored value");
            throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
    }
}

 

posted @ 2019-08-09 11:23  三只小菜鸟  阅读(1177)  评论(0编辑  收藏  举报